Imports WRDB.Controls

Public Class frmSeries
    Private GraphType As enumGraphType
    Dim XAxisList As New Generic.List(Of String)(New String() {"Bottom", "Top"})
    Dim YAxisList As New Generic.List(Of String)(New String() {"Left", "Right"})
    Private WithEvents dgvSeries As WRDB.Controls.DGVEditor
    Dim IndexList As New Generic.List(Of String)(New String() {"1", "2", "3", "4", "5"})
    Dim s As String = "None,----,-  -  -,.  .  .  .  .,-  .  -,- .  . -,Bars".Replace("-", ChrW(8212)).Replace(".", ChrW(183))

    Friend ShowOpen As Boolean = False
    Dim StyleList As New Generic.List(Of String)(s.Split(","))
    Dim SymbolList As New Generic.List(Of String)(New String() {"None", "Circle", "Square", "Diamond", "Triangle", "Triangle Dn", "Star", "XCross", "Plus", "HDash", "VDash", "Box & Whisker"})
    Dim WidthList As New Generic.List(Of String)(New String() {"0", "1", "2", "3", "4", "5"})
    Dim SizeList As New Generic.List(Of String)(New String() {"1", "2", "3", "4", "5", "6", "8", "10", "12", "14", "16"})
    Private IsClosing As Boolean = False 'so have a way of getting out of a long-running loop

    Dim MoreCriteria As New clsMoreCriteria

    Private Enum enumCol
        StaIX
        PCodX
        StaID
        PCode
        Dates
        TmWin
        TblNm
        Visbl
        XAxis
        YAxis
        YIndx
        LColr
        LStyl
        LWdth
        SType
        SSize
        SFill
        Label
    End Enum

    Private Enum enumAvgMinMax
        Avg
        Min
        Max
    End Enum

    Private Sub AddSeriesToGraph(ByVal IsLeftAxis As Boolean, ByVal AutoYAxisIndex As Boolean)
        Try
            If lstFiles.SelectedIndex = -1 Then Exit Sub
            With ActiveGraphTab()
                .Zed.Visible = False 'so cell validate event doesn't force slow refresh
                dgSeries.Visible = False
                Dim StationList As String = "", PCodeList As String = ""
                For i As Integer = 0 To lstPCodes.Items.Count - 1
                    PCodeList &= IIf(PCodeList = "", "", ";") & lstPCodes.Items(i)
                Next
                Dim DateTime As Date = Date.MinValue
                Dim DateList As String = ""

                If OpenTables(lstFiles.SelectedIndex).TableType = clsTable.enumTableType.BMD Or
                   OpenTables(lstFiles.SelectedIndex).TableType = clsTable.enumTableType.BMD2 Then .AnimationInfo.TimeWindow = enumTimeWindow.BMD

                Dim TimeWindow As enumTimeWindow = .AnimationInfo.TimeWindow

                If .GraphType = enumGraphType.Profile Then
                    Dim lst As ComboBox = cboBranch
                    Dim sbStations As New System.Text.StringBuilder
                    For i As Integer = 1 To lst.Items.Count - 1
                        sbStations.Append(IIf(i = 0, "", ";") & lst.Items(i))
                    Next
                    StationList = sbStations.ToString
                    For p As Integer = 0 To lstPCodes.SelectedItems.Count - 1
                        With OpenTables(lstFiles.SelectedIndex)
                            DateList = ListToString(.GetDateList(.GetStationList(cboBranch.Text).ToArray))
                        End With
                        If DateList <> "" Then DateTime = DateList.Split(";")(0)
                        If .AddSeries(OpenTables(lstFiles.SelectedIndex), cboBranch.Text, lstPCodes.SelectedItems(p), DateTime, TimeWindow, StationList, PCodeList, DateList, IIf(IsLeftAxis, clsSeries.enumYAxis.Left, clsSeries.enumYAxis.Right), clsSeries.enumYAxisIndex.Axis_1, AutoYAxisIndex) Then
                            AddSeriesToGrid(.Series(ActiveGraphTab.Series.Count - 1))
                            Cursor = Cursors.WaitCursor
                            WriteProgress(Owner, p, lstPCodes.SelectedItems.Count - 1, "Adding series...")
                        End If
                        If IsClosing Then Exit Sub 'set when Close button pressed
                    Next
                Else
                    Dim lst As ListBox = lstStations
                    Dim sbStations As New System.Text.StringBuilder
                    For j As Integer = 0 To lst.Items.Count - 1
                        sbStations.Append(IIf(j = 0, "", ";") & StringSplit(lst.Items(j), " - ")(0))
                    Next
                    StationList = sbStations.ToString
                    Dim i As Integer = 0, imax As Integer = lstStations.SelectedItems.Count * lstPCodes.SelectedItems.Count
                    For s As Integer = 0 To lstStations.SelectedItems.Count - 1
                        For p As Integer = 0 To lstPCodes.SelectedItems.Count - 1
                            Dim StaID As String = StringSplit(lstStations.SelectedItems(s), " - ")(0)
                            Dim PCode As String = lstPCodes.SelectedItems(p)
                            Select Case .GraphType
                                Case enumGraphType.Depth, enumGraphType.Profile, enumGraphType.Scatter, enumGraphType.Width
                                    With OpenTables(lstFiles.SelectedIndex)
                                        DateList = ListToString(.GetDateList(StaID, PCode))
                                    End With
                            End Select
                            If DateList <> "" Then DateTime = DateList.Split(";")(0)

                            If .AddSeries(OpenTables(lstFiles.SelectedIndex), StaID, PCode, DateTime, TimeWindow, StationList, PCodeList, DateList, IIf(IsLeftAxis, clsSeries.enumYAxis.Left, clsSeries.enumYAxis.Right), clsSeries.enumYAxisIndex.Axis_1, AutoYAxisIndex, MoreCriteria) Then
                                AddSeriesToGrid(.Series(ActiveGraphTab.Series.Count - 1))
                                i += 1
                                WriteProgress(Owner, i, imax, "Adding series...")
                            End If

                            If IsClosing Then Exit Sub 'set when Close button pressed
                        Next
                    Next
                End If

                If .Series.Count = 0 Then Exit Sub

                'series added is last in list; if only one series is on this axis, zoom extents
                Dim ser As clsSeries = .Series(.Series.Count - 1)
                Dim found As Boolean = False
                For i As Integer = 0 To .Series.Count - 2
                    If .Series(i).YAxis = ser.YAxis And IsLeftAxis And .Series(i).YAxisIndex = ser.YAxisIndex Then found = True : Exit For
                Next
                If Not found Then
                    If ser.YAxis = clsSeries.enumYAxis.Left Then
                        With .Zed.GraphPane.YAxisList(ser.YAxisIndex).Scale
                            .MinAuto = True
                            .MaxAuto = True
                        End With
                    Else
                        With .Zed.GraphPane.Y2AxisList(ser.YAxisIndex).Scale
                            .MinAuto = True
                            .MaxAuto = True
                        End With
                    End If
                End If
                .SetGraphTitle()
                .Zed.AxisChange()
                .Zed.Refresh()
                If .Series.Count = 1 Then ActiveGraphTab.ZoomExtents()
                If dgSeries.RowCount > 0 Then
                    dgSeries.CurrentCell = dgSeries.Item(enumCol.StaID, dgSeries.RowCount - 1)
                    dgSeries.HorizontalScrollingOffset = 0
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            ActiveGraphTab.Zed.Visible = True 'so cell validate event doesn't force slow refresh
            dgSeries.Visible = True
            EnableSeriesButtons()
            Cursor = Cursors.Default
            WriteProgress(Owner)
        End Try
    End Sub

    Private Sub AddSeriesToGrid(ByVal ser As clsSeries)
        Try
            Dim StationIDX As String = "", PCodeX As String = "", PCodeListX As String = ""
            If GraphType = enumGraphType.Scatter Then
                With CType(ser, clsSeriesScatter)
                    StationIDX = .StationIDX
                    PCodeX = .PCodeX
                    PCodeListX = .PCodeListX
                End With
            End If
            Dim TmWin As String = ""
            For Each s As String In [Enum].GetNames(GetType(enumTimeWindow))
                TmWin &= IIf(TmWin = "", "", ";") & s.Replace("_", " ").Trim
            Next
            With ser
                dgSeries.SuspendLayout()
                If .Table IsNot Nothing Then
                    Select Case .Table.TableType
                        Case clsTable.enumTableType.BMD, clsTable.enumTableType.BMD2
                            .TimeWindow = enumTimeWindow.BMD
                        Case clsTable.enumTableType.WRDBData
                            .TimeWindow = enumTimeWindow.None
                        Case Else
                            '.TimeWindow = ActiveGraphTab.AnimationInfo.TimeWindow
                    End Select
                End If
                dgSeries.RowCount += 1
                Dim r As Integer = dgSeries.Rows.Count - 1
                dgvSeries.SetCellFormatList(r, .StationList, PCodeListX, .StationList, .PCodeList, .DateList, TmWin)
                dgSeries.Item(enumCol.StaIX, r).Value = StationIDX
                dgSeries.Item(enumCol.PCodX, r).Value = PCodeX
                dgSeries.Item(enumCol.StaID, r).Value = .StationID
                dgSeries.Item(enumCol.PCode, r).Value = .PCode
                Dim dtStr As String = .DateTime
                If .DateList Is Nothing OrElse Array.IndexOf(.DateList.Split(";"), dtStr) = -1 Then .DateList &= ";" & dtStr 'add current value if not present
                dgSeries.Item(enumCol.Dates, r).Value = dtStr
                dgSeries.Item(enumCol.TmWin, r).Value = [Enum].GetName(GetType(enumTimeWindow), .TimeWindow).Replace("_", " ").Trim
                If .IsCalc Then
                    dgSeries.Item(enumCol.TblNm, r).Value = "Calculated: " & .Label
                Else
                    dgSeries.Item(enumCol.TblNm, r).Value = .Table.ToString
                End If
                dgSeries.Item(enumCol.Visbl, r).Value = .Visible
                dgSeries.Item(enumCol.XAxis, r).Value = XAxisList(.XAxis)
                dgSeries.Item(enumCol.YAxis, r).Value = YAxisList(.YAxis)
                dgSeries.Item(enumCol.YIndx, r).Value = IndexList(.YAxisIndex)
                dgSeries.Item(enumCol.LColr, r).Value = .LineColor.Name
                dgSeries.Item(enumCol.LStyl, r).Value = StyleList(.LineType + 1)
                dgSeries.Item(enumCol.LWdth, r).Value = CStr(.LineWidth)
                dgSeries.Item(enumCol.SType, r).Value = SymbolList(.SymbolType + 1)
                dgSeries.Item(enumCol.SSize, r).Value = CStr(CInt(.SymbolSize))
                dgSeries.Item(enumCol.SFill, r).Value = .SymbolFill
                dgSeries.Item(enumCol.Label, r).Value = .Label
                dgSeries.ResumeLayout(True)
            End With
            dgSeries.CurrentCell = dgSeries.Item(2, dgSeries.Rows.Count - 1)
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub btnAdd_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAddLeft.Click, btnAddRight.Click
        'if click button, add to last existing axis (unless select multiple pcodes)
        AddSeries(sender Is btnAddLeft, lstPCodes.SelectedIndices.Count > 1)
    End Sub

    ''' <summary>
    ''' Add currently selected set of stations/pcodes as series to the specified axis and axis index
    ''' </summary>
    ''' <param name="IsLeftAxis">If true, add to left axis (or bottom axis for depth profiles)</param>
    ''' <param name="AutoYAxisIndex">If true, will add additional y axes as required</param>
    ''' <remarks></remarks>
    Private Sub AddSeries(ByVal IsLeftAxis As Boolean, ByVal AutoYAxisIndex As Boolean)
        Try
            Cursor = Cursors.WaitCursor
            WriteProgress(Owner, "Adding series...")
            If GraphType = enumGraphType.Profile And cboBranch.SelectedIndex = 0 And cboBranch.Items.Count > 1 Then 'add one series for each branch
                'save selected pcodes so can be restored later
                Dim PCodeIndices As New List(Of Integer)
                Dim PCodeSelections As New List(Of String)
                For Each Index As Integer In lstPCodes.SelectedIndices
                    PCodeIndices.Add(Index)
                    PCodeSelections.Add(lstPCodes.Items(Index))
                Next

                For i As Integer = 1 To cboBranch.Items.Count - 1
                    cboBranch.SelectedIndex = i
                    lstPCodes.SelectedIndices.Clear()
                    For Each PCode As String In PCodeSelections
                        lstPCodes.SelectedItems.Add(PCode)
                    Next
                    AddSeriesToGraph(IsLeftAxis, AutoYAxisIndex)
                    WriteProgress(Owner, i, cboBranch.Items.Count - 1, "Adding series...")
                    If IsClosing Then Exit Sub 'set when Close button pressed
                Next
                cboBranch.SelectedIndex = 0

                'restore pcodes
                lstPCodes.SelectedIndices.Clear()
                For Each Index As Integer In PCodeIndices
                    lstPCodes.SelectedIndices.Add(Index)
                Next
            Else
                AddSeriesToGraph(IsLeftAxis, AutoYAxisIndex)
            End If
            If GraphType = enumGraphType.Profile Then ActiveGraphTab.ResetProfileScales()
            With CType(Owner, frmGraph)
                .EnableMenus()
                .ResetAnimationLimits()
                .EnableGraphMenus()
            End With
            If GraphType = enumGraphType.Scatter And Not AlreadyPromptedAddScatter Then
                frmInfoMsg.ShowInfoMsg("New Scatter Series", "This scatter series was initially assigned the same Station ID and PCode for both the X and Y axes (resulting in a one-to-one match on the scatter plot). Reselect the values for the X or Y axes in the series grid below to obtain the scatter graph you desire.\n\nThis message will not be repeated this session.")
                AlreadyPromptedAddScatter = True
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            dgSeries.Refresh()
            Cursor = Cursors.Default
            WriteProgress(Owner)
        End Try
    End Sub

    Private Sub btnBuild_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnBuild.Click
        With btnBuild
            mnuBuild.Show(btnBuild, .Width / 2, .Height / 2)
        End With
    End Sub

    Private Sub btnCalculate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnCalculate.Click
        Try
            With btnCalculate
                mnuCalcConst.Visible = False
                mnuCalcVariable.Visible = False
                mnuCalcMovingAvg.Visible = False
                mnuCalcDailyAvgMinMax.Visible = False
                mnuCalcSine.Visible = False

                mnuCalcSepAdd.Visible = False
                mnuCalcAdd.Visible = False
                mnuCalcSubtract.Visible = False

                mnuCalcSepAdjust.Visible = False
                mnuCalcAdjust.Visible = False
                mnuCalcDuplicate.Visible = False
                mnuCalcEdit.Visible = False

                mnuCalcScatterOneToOne.Visible = False
                mnuCalcScatterLinear.Visible = False
                mnuCalcScatterPower.Visible = False
                mnuCalcScatterExponential.Visible = False

                mnuCalcProbNormal.Visible = False
                mnuCalcProbLogNormal.Visible = False
                mnuCalcProbLogPearson.Visible = False

                Select Case GraphType
                    Case enumGraphType.TimeSeries
                        mnuCalcSepAdd.Visible = True
                        mnuCalcAdd.Visible = True
                        mnuCalcSubtract.Visible = True
                        mnuCalcAdd.Enabled = ActiveGraphTab.Series.Count >= 2
                        mnuCalcSubtract.Enabled = ActiveGraphTab.Series.Count >= 2

                        mnuCalcSepAdjust.Visible = True
                        mnuCalcAdjust.Visible = True
                        mnuCalcDuplicate.Visible = True
                        mnuCalcEdit.Visible = True

                        mnuCalcConst.Visible = True
                        mnuCalcVariable.Visible = True
                        mnuCalcDailyAvgMinMax.Visible = True
                        mnuCalcMovingAvg.Visible = True
                        mnuCalcSine.Visible = True

                    Case enumGraphType.Profile, enumGraphType.Depth, enumGraphType.Width
                        mnuCalcSepAdd.Visible = True
                        mnuCalcAdd.Visible = True
                        mnuCalcSubtract.Visible = True
                        mnuCalcAdd.Enabled = ActiveGraphTab.Series.Count >= 2
                        mnuCalcSubtract.Enabled = ActiveGraphTab.Series.Count >= 2

                        mnuCalcSepAdjust.Visible = True
                        mnuCalcAdjust.Visible = True
                        mnuCalcDuplicate.Visible = True
                        mnuCalcEdit.Visible = True
                        mnuCalcConst.Visible = True
                        mnuCalcVariable.Visible = True

                    Case enumGraphType.Scatter
                        mnuCalcScatterOneToOne.Visible = True
                        mnuCalcScatterLinear.Visible = True
                        mnuCalcScatterPower.Visible = True
                        mnuCalcScatterExponential.Visible = True

                    Case enumGraphType.Histogram

                    Case enumGraphType.Probability
                        mnuCalcProbNormal.Visible = True
                        mnuCalcProbLogNormal.Visible = True
                        mnuCalcProbLogPearson.Visible = True

                    Case enumGraphType.FlowDuration

                End Select
                mnuCalc.Show(btnCalculate, .Width / 2, .Height / 2)
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub btnClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnClose.Click
        'in case in middle of long build operation and want to interrupt, closing form will simulate clicking on status bar which cancels
        GraphForm.Statusbar.PerformClick()
        IsClosing = True
        Close()
    End Sub

    Private Sub btnFileClose_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFileClose.Click
        Try
            For Each tbl As clsTable In OpenTables
                If lstFiles.Text = tbl.ToString Then
                    'remove all series that reference table
                    For j As Integer = 0 To GraphForm.TabGraph.TabCount - 2
                        With CType(GraphForm.TabGraph.TabPages(j).Tag, clsGraphTab)
                            For k As Integer = .Series.Count - 1 To 0 Step -1
                                If Not .Series(k).IsCalc AndAlso .Series(k).Table.Equals(tbl) Then
                                    .Series(k).RemoveZedCurve()
                                    .Series.RemoveAt(k)
                                End If
                            Next
                            .ResetYAxes()
                            .Zed.AxisChange()
                            .Zed.Refresh()
                        End With
                    Next

                    'calibration datasets that reference table will NOT automatically be removed

                    tbl.Dispose()
                    OpenTables.Remove(tbl)
                    Exit For
                End If
            Next

            lstFiles.Items.Clear()
            For Each tbl As clsTable In OpenTables
                lstFiles.Items.Add(tbl.ToString)
            Next

            dgSeries.Rows.Clear()
            For Each ser As clsSeries In ActiveGraphTab.Series
                AddSeriesToGrid(ser)
            Next
            cboBranch.Items.Clear()
            lstStations.Items.Clear()
            lstPCodes.Items.Clear()
            lstFiles.SelectedIndex = -1
            If lstFiles.Items.Count > 0 Then lstFiles.SelectedIndex = 0
            UpdateTableList()
            GraphForm.FillSeriesList()
            GraphForm.SetAxisProperties()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub btnFileOpen_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnFileOpen.Click
        Try
            Dim s As String = GetSetting(REGAPP, "General", "LastOpenType", "File")
            Dim lastDir As String = GetSetting(REGAPP, "General", "LastOpenDir", "")
            Dim lstTbls As New Generic.List(Of clsTable)
            If s.ToUpper = "FILE" Then
                With New OpenFileDialog
                    .Multiselect = True
                    .Filter = "All Data File Formats|*.bmd;*.bmd2;*.db;*.txt;*.csv;*.dbf;*.mdb;*.xls*;*.sdb;*.fdb;*.out;*.air|" &
                        "Binary Modeling Data Files|*.bmd|" &
                        "Binary Modeling Data Files Ver 2|*.bmd2|" &
                        "Paradox Tables|*.db|" &
                        "Tab-delimited Text Files|*.txt|" &
                        "Comma-separated Text Files|*.csv|" &
                        "dBase Tables|*.dbf|" &
                        "Access Database Files|*.mdb|" &
                        "Excel Workbook Files|*.xls*|" &
                        "Sqlite Database Files|*.sdb|" &
                        "Firebird Database Files|*.fdb|" &
                        "SQL Server CE Database Files|*.sdf|" &
                        "LSPC Output Files|*.out|" &
                        "LSPC Input Files|*.air|" &
                        "EFDC Output Files|*.out"
                    .FilterIndex = GetSetting(REGAPP, Me.Name, "FileFilter", 0)
                    .InitialDirectory = lastDir
                    .CheckFileExists = True
                    .Title = "Open Data File"
                    If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                        SaveSetting(REGAPP, Me.Name, "FileFilter", .FilterIndex)
                        SaveSetting(REGAPP, "General", "LastOpenDir", IO.Path.GetDirectoryName(.FileName))
                        For i As Integer = 0 To .FileNames.Length - 1
                            Cursor = Cursors.WaitCursor
                            WriteProgress(GraphForm, "Loading {0}...", .SafeFileNames(i))
                            lstTbls.Add(New clsTable(GroupFileName, ExpertMode))
                            Select Case IO.Path.GetExtension(.SafeFileNames(i).ToUpper)
                                Case ".BMD"
                                    If Not lstTbls(i).CreateBMDTable(.FileNames(i)) Then Exit Sub
                                    lstTbls(i).TableName = .SafeFileNames(i)
                                Case ".BMD2"
                                    If Not lstTbls(i).CreateBMD2Table(.FileNames(i)) Then Exit Sub
                                    lstTbls(i).TableName = .SafeFileNames(i)
                                Case ".OUT", ".AIR"
                                    If i > 0 Then Exit For
                                    If Not lstTbls(i).CreateWRDBDataTable(.FileNames(0)) Then Exit Sub
                                Case ".TXT", ".CSV", ".DBF", ".DB"
                                    If Not lstTbls(i).CreateDatabaseFileTable(IO.Path.GetDirectoryName(.FileNames(i)), .SafeFileNames(i)) Then Exit Sub
                                Case Else
                                    'If Not tbl.CreateOtherProject(IO.Path.GetDirectoryName(.FileNames(i)), .SafeFileNames(i)) Then Exit Sub
                                    Dim FileName As String = .FileNames(0)
                                    With New frmSelectTable(FileName, ExpertMode)
                                        .InitialDatabaseFile = FileName
                                        If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                                            lstTbls = .SelectedTables
                                        Else
                                            lstTbls.Clear()
                                        End If
                                        .Dispose()
                                        If lstTbls.Count = 0 Then Exit Sub
                                    End With
                            End Select
                        Next
                        For Each tbl As clsTable In lstTbls
                            Dim found As Boolean = False
                            For Each t As clsTable In OpenTables
                                If t.Equals(tbl) Then
                                    Application.UseWaitCursor = False
                                    If Not ExpertMode Then WarningMsg("You cannot add a given project or table more than once.")
                                    found = True
                                    Exit For
                                End If
                            Next
                            If Not found Then
                                OpenTables.Add(tbl)
                                lstFiles.Items.Add(tbl.ToString)
                                lstFiles.SelectedIndex = lstFiles.Items.Count - 1
                            End If
                        Next
                    Else
                        Exit Sub
                    End If
                End With
            Else
                With New frmSelectTable(s, ExpertMode)
                    If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then lstTbls = .SelectedTables
                    .Dispose()
                    If lstTbls.Count = 0 Then Exit Sub
                End With
                For Each tbl As clsTable In lstTbls
                    Dim foundSameTable As Boolean = False
                    For Each t As clsTable In OpenTables
                        If t.Equals(tbl) Then
                            Application.UseWaitCursor = False
                            If Not ExpertMode Then WarningMsg("You cannot add a given project or table more than once.")
                            foundSameTable = True
                            Exit For
                        End If
                    Next
                    If Not foundSameTable Then
                        WriteProgress(GraphForm, "Loading {0}...", tbl.TableName)
                        OpenTables.Add(tbl)
                        lstFiles.Items.Add(tbl.ToString)
                    End If
                Next
                lstFiles.SelectedIndex = lstFiles.Items.Count - 1
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Cursor = Cursors.Default
            WriteProgress(GraphForm)
            UpdateTableList()
        End Try
    End Sub

    Private Sub btnSeriesRemove_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnSeriesRemove.Click
        Try
            With ActiveGraphTab.Zed.GraphPane
                'get list of unique rows that are currently selected
                Dim RowList As New Generic.List(Of Integer)
                For Each c As DataGridViewCell In dgSeries.SelectedCells
                    If Not RowList.Contains(c.RowIndex) Then RowList.Add(c.RowIndex)
                Next
                For r As Integer = dgSeries.RowCount - 1 To 0 Step -1
                    If RowList.Contains(r) Then
                        dgSeries.Rows.RemoveAt(r)
                        Dim ser As clsSeries = ActiveGraphTab.Series(r)
                        ser.RemoveZedCurve()
                        ActiveGraphTab.Series.Remove(ser)
                    End If
                Next
                dgSeries.HorizontalScrollingOffset = 0
                If dgSeries.RowCount = 0 Then
                    For i As Integer = 1 To 4
                        .YAxisList(i).IsVisible = False
                        .Y2AxisList(i).IsVisible = False
                    Next
                    .YAxis.IsVisible = True
                    .Y2Axis.IsVisible = False
                End If
            End With
            With ActiveGraphTab()
                .SetGraphTitle()
                .ResetYAxes()
                .Zed.AxisChange()
                .Zed.Refresh()
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub btnSeriesSelect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSeriesSelect.Click
        Try
            dgSeries.SelectionMode = DataGridViewSelectionMode.RowHeaderSelect
            For i As Integer = 0 To dgSeries.RowCount - 1
                dgSeries.Rows(i).Selected = True
            Next
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub SelectAllStations()
        Try
            With lstStations
                .SelectedItems.Clear()
                For i As Integer = 0 To .Items.Count - 1
                    .Tag = IIf(i < .Items.Count - 1, "Busy", "") 'so pcode doesn't get updated until the end
                    .SelectedItems.Add(.Items(i))
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub SelectAllPCodes()
        Try
            With lstPCodes
                .SelectedItems.Clear()
                For i As Integer = 0 To .Items.Count - 1
                    .SelectedItems.Add(.Items(i))
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Selected branch has changed; fill both Stations and PCode lists
    ''' </summary>
    Private Sub cboBranch_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboBranch.SelectedIndexChanged
        Try
            Cursor = Cursors.WaitCursor
            WriteProgress(Owner, "Updating display...")
            If lstFiles.SelectedIndex = -1 Then Exit Sub
            txtStations.Clear()
            FillStationList()
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Cursor = Cursors.Default
            WriteProgress(Owner)
        End Try
    End Sub

    Private Sub FillStationList()
        If lstFiles.SelectedIndex = -1 Then Exit Sub
        With OpenTables(lstFiles.SelectedIndex)
            btnAddLeft.Enabled = False
            btnAddRight.Enabled = False
            lstPCodes.Items.Clear()
            lstStations.BeginUpdate()
            lstStations.Items.Clear()
            If txtStations.Text.Trim = "" AndAlso lstFiles.Text = GraphForm.SeriesLists.TableName AndAlso cboBranch.SelectedIndex = 0 Then
                lstStations.Items.AddRange(GraphForm.SeriesLists.Stations) 'fill from cache so is faster
            Else
                For Each s As String In .GetStationList(cboBranch.Text)
                    If s.ToUpper Like "*" & txtStations.Text.ToUpper & "*" Then lstStations.Items.Add(s)
                Next
            End If
            If lstStations.Items.Count > 0 Then lstStations.SelectedIndex = 0
            lstStations.EndUpdate()
            If lstPCodes.Items.Count > 0 Then lstPCodes.SelectedIndex = 0
            'btnStationsSelect.Enabled = GraphType <> enumGraphType.Profile
            'btnPCodesSelect.Enabled = True
            txtStations.Enabled = txtStations.Text.Trim <> "" OrElse (lstStations.Items.Count > 0 AndAlso GraphType <> enumGraphType.Profile)
            btnStations.Enabled = txtStations.Text.Trim <> "" OrElse (lstStations.Items.Count > 0 AndAlso GraphType <> enumGraphType.Profile)
            btnPCodes.Enabled = True
        End With
    End Sub

    Private Sub cboGraphType_SelectedIndexChanged(sender As Object, e As System.EventArgs) Handles cboGraphType.SelectedIndexChanged
        If cboGraphType.Text = "Depth Profile" Then
            btnAddLeft.Text = "   Add to\n&Bottom Axis".Replace("\n", vbCr)
            btnAddLeft.Image = My.Resources.gotonextmsg
            btnAddRight.Text = "   Add to\n&  Top Axis   ".Replace("\n", vbCr)
            btnAddRight.Image = My.Resources.GoToPreviousMessage
        Else
            btnAddLeft.Text = "   Add to\n&Left Axis  ".Replace("\n", vbCr)
            btnAddLeft.Image = My.Resources.GoToPrevious
            btnAddRight.Text = "   Add to\n&Right Axis".Replace("\n", vbCr)
            btnAddRight.Image = My.Resources.GoToNextHS
        End If
    End Sub

    Private Sub cboGraphType_SelectionChangeCommitted(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboGraphType.SelectionChangeCommitted
        Try
            If Not GraphForm.ChangeGraphType(cboGraphType.SelectedIndex) Then cboGraphType.SelectedIndex = ActiveGraphTab.GraphType : Exit Sub

            GraphType = ActiveGraphTab.GraphType

            With dgSeries
                Select Case GraphType
                    Case enumGraphType.Scatter
                        .Columns(enumCol.StaIX).Visible = True
                        .Columns(enumCol.PCodX).Visible = True
                    Case Else
                        .Columns(enumCol.StaIX).Visible = False
                        .Columns(enumCol.PCodX).Visible = False
                End Select
                ActiveGraphTab.AnimationInfo.ToolbarVisible = False
                Select Case GraphType
                    Case enumGraphType.Depth, enumGraphType.Profile, enumGraphType.Width
                        .Columns(enumCol.Dates).Visible = True
                        .Columns(enumCol.TmWin).Visible = True
                        'For Each tbl As clsTable In OpenTables
                        '    If tbl.TableType = clsTable.enumTableType.BMD Or
                        '       tbl.TableType = clsTable.enumTableType.BMD2 Then
                        '        ActiveGraphTab.AnimationInfo.ToolbarVisible = True
                        '        GraphForm.ResetAnimationLimits()
                        '        Exit For
                        '    End If
                        'Next
                        ActiveGraphTab.AnimationInfo.ToolbarVisible = True
                        GraphForm.ResetAnimationLimits()
                    Case enumGraphType.Scatter
                        .Columns(enumCol.Dates).Visible = False
                        .Columns(enumCol.TmWin).Visible = True
                    Case Else
                        .Columns(enumCol.Dates).Visible = False
                        .Columns(enumCol.TmWin).Visible = False
                End Select
                Dim StationIDY As String = "Station ID", PCodeY As String = "PCode"
                Select Case GraphType
                    Case enumGraphType.Profile : StationIDY = "Branch ID"
                    Case enumGraphType.Scatter : StationIDY = "Station ID Y" : PCodeY = "PCode Y"
                End Select
                .Columns(enumCol.StaID).HeaderText = StationIDY
                .Columns(enumCol.PCode).HeaderText = PCodeY
            End With

            txtStations.Clear()
            txtPCodes.Clear()
            lstStations.Enabled = GraphType <> enumGraphType.Profile
            txtStations.Enabled = GraphType <> enumGraphType.Profile
            btnStations.Enabled = GraphType <> enumGraphType.Profile
            dgSeries.Rows.Clear()
            EnableSeriesButtons()
            With CType(Owner, frmGraph)
                .EnableMenus()
                .EnableGraphMenus()
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

#Region "Calculate routines"

    ''' <summary>
    ''' Calculate new series based on items in first series pluss or minus those in second
    ''' </summary>
    ''' <param name="crv1"></param>
    ''' <param name="crv2"></param>
    Private Sub ComputeAdd_Subtract(ByVal IsAdd As Boolean, ByRef crv1 As ZedGraph.LineItem, ByVal crv2 As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            Cursor = Cursors.WaitCursor
            WriteProgress(Me, "Computing differences...")

            'copy x and y values to array so can do binary search
            Dim x(crv2.NPts - 1), y(crv2.NPts - 1) As Double
            For i As Integer = 0 To crv2.NPts - 1
                x(i) = crv2.Points(i).X
                y(i) = crv2.Points(i).Y
            Next
            Array.Sort(x, y)

            For i As Integer = 0 To crv1.NPts - 1
                Dim x1 As Double = crv1.Points(i).X
                Dim y1 As Double = crv1.Points(i).Y
                Dim y2 As Double
                Dim i2 As Integer = Array.BinarySearch(x, x1)
                If i2 >= 0 Then 'exact match
                    y2 = y(i2)
                Else 'bitwise compliment is next larger value
                    i2 = i2 Xor -1
                    If i2 = 0 Or i2 >= crv2.NPts Then
                        y2 = Double.MinValue
                    Else
                        With crv2.Points(i2 - 1)
                            y2 = .Y + (x1 - .X) / (x(i2) - .X) * (y(i2) - .Y)
                        End With
                    End If
                End If
                If y2 <> Double.MinValue Then
                    newcrv.AddPoint(x1, y1 + y2 * IIf(IsAdd, 1, -1))
                End If
                WriteProgress(Me, i, crv1.NPts - 1, "Computing differences...")
            Next
            If newcrv.NPts = 0 Then WarningMsg("Differences could not be computed; the series do not overlap in time.") : Exit Sub

            Dim sumy As Double = 0
            Dim sumy2 As Double = 0
            Dim sumyabs As Double = 0
            Dim miny As Double = Double.MaxValue
            Dim maxy As Double = Double.MinValue
            With newcrv
                For i As Integer = 0 To .NPts - 1
                    With newcrv.Points(i)
                        sumy += .Y
                        sumy2 += .Y * .Y
                        sumyabs += Math.Abs(.Y)
                        miny = Math.Min(miny, .Y)
                        maxy = Math.Max(maxy, .Y)
                    End With
                Next i
                If IsAdd Then
                    FitStats = String.Format("Add: First Series = {0}\nSecond Series = {1}\nCount = {2}", crv1.Label.Text, crv2.Label.Text, .NPts)
                Else
                    FitStats = String.Format("Subtract: First Series = {0}\nSecond Series = {1}\nCount = {2}\nMean Deviation = {3:N5}\nMean Abs. Dev. = {4:N5}\nMin Deviation =  {5:N5}\nMax Deviation =  {6:N5}\nRMS Deviation =  {7:N5}", crv1.Label.Text, crv2.Label.Text, .NPts, sumy / .NPts, sumyabs / .NPts, miny, maxy, Math.Sqrt(sumy2 / .NPts))
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Cursor = Cursors.Default
            WriteProgress(Me)
        End Try
    End Sub

    Shared Sub ComputeAdjust(ByVal Value As String, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            With basecrv
                For i As Integer = 0 To .NPts - 1
                    Dim y As Double
                    With .Points(i)
                        Dim c As Double = Val(Value.Substring(1))
                        Select Case Value.Substring(0, 1)
                            Case "+" : y = .Y + c
                            Case "-" : y = .Y - c
                            Case "*" : y = .Y * c
                            Case "/" : y = .Y / c
                            Case Else
                                Throw New ApplicationException("The Value argument in ComputeAdjust must be a string with the first character an arithmetic operator.")
                        End Select
                        newcrv.AddPoint(.X, y)
                    End With
                Next
                FitStats = "Adjust = " & Value
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ComputeConstant(ByVal Value As Double, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            With basecrv
                If ActiveGraphTab.GraphType = enumGraphType.Depth Then
                    newcrv.AddPoint(Value, .Item(0).Y)
                    newcrv.AddPoint(Value, .Item(.NPts - 1).Y)
                Else
                    newcrv.AddPoint(.Item(0).X, Value)
                    newcrv.AddPoint(.Item(.NPts - 1).X, Value)
                End If
                FitStats = "Constant = " & Value
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ComputeVariable(dt As DataTable, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            With basecrv
                For Each r As DataRow In dt.Rows
                    If Not IsDBNull(r(0)) And Not IsDBNull(r(1)) Then
                        If dt.Columns(0).DataType Is GetType(DateTime) Then
                            newcrv.AddPoint(CType(r(0), DateTime).ToOADate, r(1))
                        Else
                            newcrv.AddPoint(r(0), r(1))
                        End If
                    End If
                Next
                FitStats = "Num Points = " & newcrv.NPts
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Shared Sub ComputeLimits(ByVal MinLimit As Double, ByVal MaxLimit As Double, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            With basecrv
                If ActiveGraphTab.GraphType = enumGraphType.Depth Then
                    'let it span time almost entire x axis scale, not just for this series
                    Dim ymin As Double = ActiveGraphTab.Zed.GraphPane.YAxis.Scale.Min
                    Dim ymax As Double = ActiveGraphTab.Zed.GraphPane.YAxis.Scale.Max
                    Dim maxmin As Double = ymax - ymin
                    If .NPts > 0 Then
                        ymin = Math.Min(ymin + maxmin * 0.1, .Item(0).Y)
                        ymax = Math.Max(ymax - maxmin * 0.1, .Item(.NPts - 1).Y)
                    End If

                    If MinLimit <> Double.MinValue Then
                        newcrv.AddPoint(MinLimit, ymin)
                        newcrv.AddPoint(MinLimit, ymax)
                        newcrv.AddPoint(Double.MaxValue, Double.MaxValue)
                    End If
                    If MaxLimit <> Double.MaxValue Then
                        newcrv.AddPoint(MaxLimit, ymin)
                        newcrv.AddPoint(MaxLimit, ymax)
                    End If
                Else
                    'let it span time almost entire x axis scale, not just for this series
                    Dim xmin As Double = ActiveGraphTab.Zed.GraphPane.XAxis.Scale.Min
                    Dim xmax As Double = ActiveGraphTab.Zed.GraphPane.XAxis.Scale.Max
                    Dim maxmin As Double = xmax - xmin
                    If .NPts > 0 Then
                        xmin = Math.Min(xmin + maxmin * 0.1, .Item(0).X)
                        xmax = Math.Max(xmax - maxmin * 0.1, .Item(.NPts - 1).X)
                    End If

                    If MinLimit <> Double.MinValue Then
                        newcrv.AddPoint(xmin, MinLimit)
                        newcrv.AddPoint(xmax, MinLimit)
                        newcrv.AddPoint(Double.MaxValue, Double.MaxValue)
                    End If
                    If MaxLimit <> Double.MaxValue Then
                        newcrv.AddPoint(xmin, MaxLimit)
                        newcrv.AddPoint(xmax, MaxLimit)
                    End If
                End If
                Dim s As String = ""
                If MinLimit <> Double.MinValue Then s = MinLimit
                If MaxLimit <> Double.MaxValue Then s &= IIf(s = "", "", " and ") & MaxLimit
                FitStats = String.Format("Criteria Limits = {0}", s)
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Shared Sub ComputeLimits(GraphType As enumGraphType, ByRef Criteria As WRDB.Project.clsCriteria, StationID As String, PCode As String, DateTime As Date,
                             ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            Dim MinLimit, MaxLimit As Double
            Dim MinLimit2, MaxLimit2 As Double

            With basecrv
                If GraphType = enumGraphType.TimeSeries Then

                    'plot possibly time variable limits
                    Dim dtMin As Date = Date.FromOADate(ActiveGraphTab.Zed.GraphPane.XAxis.Scale.Min)
                    dtMin = New Date(dtMin.Year, dtMin.Month, 1)
                    Dim dtMax As Date = Date.FromOADate(ActiveGraphTab.Zed.GraphPane.XAxis.Scale.Max).AddDays(-1)
                    dtMax = New Date(dtMax.Year, dtMax.Month, 1).AddMonths(1).AddDays(-1)
                    Dim dt As Date = New Date(dtMin.Year, dtMin.Month, 1)
                    Dim isVariable As Boolean = False

                    While dt <= dtMax
                        If Criteria.TryGetCriteria(StationID, PCode, dt, "", 0.0, MinLimit, MaxLimit) AndAlso MinLimit <> Double.MinValue Then
                            newcrv.AddPoint(dt.ToOADate, MinLimit)
                        Else
                            Exit While
                        End If
                        dt = dt.AddMonths(1).AddDays(-1)
                        If Criteria.TryGetCriteria(StationID, PCode, dt, "", 0.0, MinLimit2, MaxLimit2) AndAlso MinLimit2 <> Double.MinValue Then
                            newcrv.AddPoint(dt.ToOADate, MinLimit)
                        End If
                        If MinLimit <> MinLimit2 Or MaxLimit <> MaxLimit2 Then isVariable = True
                        dt = dt.AddDays(1)
                    End While
                    newcrv.AddPoint(Double.MaxValue, Double.MaxValue)
                    dt = New Date(dtMin.Year, dtMin.Month, 1)
                    While dt <= dtMax
                        If Criteria.TryGetCriteria(StationID, PCode, dt, "", 0.0, MinLimit, MaxLimit) AndAlso MaxLimit <> Double.MaxValue Then
                            newcrv.AddPoint(dt.ToOADate, MaxLimit)
                        Else
                            Exit Sub
                        End If
                        dt = dt.AddMonths(1).AddDays(-1)
                        If Criteria.TryGetCriteria(StationID, PCode, dt, "", 0.0, MinLimit, MaxLimit) AndAlso MaxLimit <> Double.MaxValue Then
                            newcrv.AddPoint(dt.ToOADate, MaxLimit)
                        Else
                            Exit Sub
                        End If
                        dt = dt.AddDays(1)
                    End While
                    Dim s As String = ""
                    If MinLimit <> Double.MinValue Then s = MinLimit
                    If MaxLimit <> Double.MaxValue Then s &= IIf(s = "", "", " and ") & MaxLimit
                    If isVariable Then s = "Variable"
                    FitStats = String.Format("Criteria Limits = {0}", s)
                Else
                    If Criteria.TryGetCriteria(StationID, PCode, DateTime, "", 0, MinLimit, MaxLimit) Then

                        If GraphType = enumGraphType.Depth Then
                            'let it span time almost entire x axis scale, not just for this series
                            Dim ymin As Double = ActiveGraphTab.Zed.GraphPane.YAxis.Scale.Min
                            Dim ymax As Double = ActiveGraphTab.Zed.GraphPane.YAxis.Scale.Max
                            Dim maxmin As Double = ymax - ymin
                            If .NPts > 0 Then
                                ymin = Math.Min(ymin + maxmin * 0.1, .Item(0).Y)
                                ymax = Math.Max(ymax - maxmin * 0.1, .Item(.NPts - 1).Y)
                            End If

                            If MinLimit <> Double.MinValue Then
                                newcrv.AddPoint(MinLimit, ymin)
                                newcrv.AddPoint(MinLimit, ymax)
                                newcrv.AddPoint(Double.MaxValue, Double.MaxValue)
                            End If
                            If MaxLimit <> Double.MaxValue Then
                                newcrv.AddPoint(MaxLimit, ymin)
                                newcrv.AddPoint(MaxLimit, ymax)
                            End If
                        ElseIf GraphType = enumGraphType.Scatter Then
                            'don't plot limit lines for scatter
                        Else
                            'let it span time almost entire x axis scale, not just for this series
                            Dim xmin As Double = ActiveGraphTab.Zed.GraphPane.XAxis.Scale.Min
                            Dim xmax As Double = ActiveGraphTab.Zed.GraphPane.XAxis.Scale.Max
                            Dim maxmin As Double = xmax - xmin
                            If .NPts > 0 Then
                                xmin = Math.Min(xmin + maxmin * 0.1, .Item(0).X)
                                xmax = Math.Max(xmax - maxmin * 0.1, .Item(.NPts - 1).X)
                            End If

                            If MinLimit <> Double.MinValue Then
                                newcrv.AddPoint(xmin, MinLimit)
                                newcrv.AddPoint(xmax, MinLimit)
                                newcrv.AddPoint(Double.MaxValue, Double.MaxValue)
                            End If
                            If MaxLimit <> Double.MaxValue Then
                                newcrv.AddPoint(xmin, MaxLimit)
                                newcrv.AddPoint(xmax, MaxLimit)
                            End If
                        End If

                        Dim s As String = ""
                        If MinLimit <> Double.MinValue Then s = MinLimit
                        If MaxLimit <> Double.MaxValue Then s &= IIf(s = "", "", " and ") & MaxLimit
                        FitStats = String.Format("Criteria Limits = {0}", s)
                    Else
                        Exit Sub
                    End If
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ComputeMovingAvg(ByVal Period As Integer, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            With basecrv
                If Period > .NPts Then ErrorMsg("Insufficient data to compute moving average.") : Exit Sub
                For i As Integer = Int((Period + 1) / 2) To .NPts - Int(Period / 2)
                    Dim sumy As Double = 0
                    Dim ycount As Integer = 0
                    For j As Integer = i - Period \ 2 To i + Period \ 2
                        If j >= 0 And j < .NPts Then
                            sumy = sumy + .Points(j).Y
                            ycount += 1
                        End If
                    Next j
                    newcrv.AddPoint(.Points(i).X, sumy / ycount)
                Next i
                FitStats = "Num Points = " & Period
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ComputeDailyAvgMinMax(ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String, AvgMinMax As enumAvgMinMax, TimeOfDay As String)
        Try
            With basecrv
                Dim i As Integer = 0
                Dim lastDate, thisdate As Date
                Dim sumy As Double
                Dim miny As Double = Double.MaxValue, maxy As Double = Double.MinValue
                Dim ymin As Double = Double.MaxValue, ymax As Double = Double.MinValue 'min/max within given day
                Dim ycount As Integer
                Do While i <= .NPts - 1
                    thisdate = Date.FromOADate(.Points(i).X)
                    If i = 0 OrElse (thisdate.DayOfYear <> lastDate.DayOfYear Or thisdate.Year <> lastDate.Year) Then 'day has changed, restart average
                        If i > 0 Then 'save previous
                            Select Case AvgMinMax
                                Case enumAvgMinMax.Avg
                                    newcrv.AddPoint((New Date(lastDate.Year, lastDate.Month, lastDate.Day, TimeOfDay.Split(":")(0), TimeOfDay.Split(":")(1), 0)).ToOADate, sumy / ycount)
                                    miny = Math.Min(miny, sumy / ycount)
                                    maxy = Math.Max(maxy, sumy / ycount)
                                    sumy = 0
                                    ycount = 0
                                Case enumAvgMinMax.Min
                                    newcrv.AddPoint((New Date(lastDate.Year, lastDate.Month, lastDate.Day, TimeOfDay.Split(":")(0), TimeOfDay.Split(":")(1), 0)).ToOADate, ymin)
                                    miny = Math.Min(miny, ymin)
                                    maxy = Math.Max(maxy, ymin)
                                    ymin = Double.MaxValue
                                Case enumAvgMinMax.Max
                                    newcrv.AddPoint((New Date(lastDate.Year, lastDate.Month, lastDate.Day, TimeOfDay.Split(":")(0), TimeOfDay.Split(":")(1), 0)).ToOADate, ymax)
                                    miny = Math.Min(miny, ymax)
                                    maxy = Math.Max(maxy, ymax)
                                    ymax = Double.MinValue
                            End Select
                        End If
                    End If
                    Select Case AvgMinMax
                        Case enumAvgMinMax.Avg
                            sumy += .Points(i).Y
                        Case enumAvgMinMax.Min
                            ymin = Math.Min(ymin, .Points(i).Y)
                        Case enumAvgMinMax.Max
                            ymax = Math.Max(ymax, .Points(i).Y)
                    End Select
                    ycount += 1
                    lastDate = Date.FromOADate(.Points(i).X)
                    i += 1
                Loop
                newcrv.AddPoint((New Date(thisdate.Year, thisdate.Month, thisdate.Day, TimeOfDay.Split(":")(0), TimeOfDay.Split(":")(1), 0)).ToOADate, Choose(AvgMinMax + 1, sumy / ycount, ymin, ymax))
                miny = Math.Min(miny, sumy / ycount)
                maxy = Math.Max(maxy, sumy / ycount)
                FitStats = String.Format("Count: {0}\nMin Value: {1}\nMax Value: {2}", newcrv.NPts, Math.Round(miny, 4), Math.Round(maxy, 4))
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub ComputeSineCurve(ByRef SineCurve As structSineCurve, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            Dim X() As Double
            Dim Y() As Double 'time and value
            Dim xmin As Double = Double.MaxValue, xmax As Double = Double.MinValue
            Const NumUnknowns As Short = 3 'number of unknowns
            Dim SDev(NumUnknowns) As Double 'fitted parameters and standard deviations
            With basecrv
                ReDim X(.NPts - 1)
                ReDim Y(.NPts - 1)
                For i As Integer = 0 To .NPts - 1
                    X(i) = .Points(i).X
                    Y(i) = .Points(i).Y
                    xmin = Math.Min(xmin, X(i))
                    xmax = Math.Max(xmax, X(i))
                Next i
            End With
            Application.DoEvents()

            With SineCurve
                modNLFit.ComputeSineCurve(X, Y, .Mean, .Amplitude, .PhaseAngle, .Period, .StdDev, .Description)
                'sine curve will range from minimum to maximum value; one value per day
                For xx As Double = xmin To xmax
                    newcrv.AddPoint(xx, .Mean + .Amplitude * (Math.Sin(2 * Math.PI / .Period * xx + .PhaseAngle)))
                Next
                FitStats = .Description
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Friend Shared Sub ComputeScatterFit(ByVal FitType As enumCurveFit, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            Dim Stats As structFitStats
            If ComputeLeastSquares(basecrv, FitType, Stats) Then
                With Stats
                    Select Case FitType
                        Case enumCurveFit.Linear
                            newcrv.AddPoint(.XMin, .Intercept + .Slope * .XMin)
                            newcrv.AddPoint(.XMax, .Intercept + .Slope * .XMax)
                        Case enumCurveFit.OneToOne
                            newcrv.AddPoint(.XMin, .XMin)
                            newcrv.AddPoint(.XMax, .XMax)
                        Case Else
                            For x As Double = .XMin To .XMax Step (.XMax - .XMin) / 1000
                                Select Case FitType
                                    Case enumCurveFit.Power : newcrv.AddPoint(x, Math.Exp(.Intercept + .Slope * (Math.Log(x))))
                                    Case enumCurveFit.Exponential : newcrv.AddPoint(x, Math.Exp(.Intercept + .Slope * x))
                                End Select
                            Next
                    End Select
                    If FitType = enumCurveFit.OneToOne Then
                        FitStats = String.Format("Num Pts = {0}", .NumPoints)
                    Else
                        FitStats = String.Format("Num Pts = {0}\nSlope = {1:N3}\nIntercept = {2:N3}\nCorr. Coef = {3:N3}", .NumPoints, .Slope, .Intercept, .CorrCoef)
                    End If
                End With
            Else
                If Not ExpertMode Then WarningMsg("Unable to compute least squares fit.")
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Friend Shared Function ComputeLeastSquares(ByVal SourceCurve As ZedGraph.LineItem, ByVal FitType As enumCurveFit, ByRef Stats As structFitStats) As Boolean
        Try
            With Stats
                .XMin = Double.MaxValue
                .XMax = Double.MinValue
                .YMin = Double.MaxValue
                .YMax = Double.MinValue

                Dim SumX As Double = 0, SumX2 As Double = 0, SumY As Double = 0, SumY2 As Double = 0, SumXY As Double = 0

                .NumPoints = SourceCurve.NPts
                For i As Integer = 0 To .NumPoints - 1
                    Dim X As Double = SourceCurve.Points(i).X
                    Dim Y As Double = SourceCurve.Points(i).Y
                    .XMin = Math.Min(.XMin, X)
                    .XMax = Math.Max(.XMax, X)
                    .YMin = Math.Min(.YMin, Y)
                    .YMax = Math.Max(.YMax, Y)

                    Select Case FitType
                        Case enumCurveFit.Linear, enumCurveFit.OneToOne
                        Case enumCurveFit.Power
                            If X <= 0 Or Y <= 0 Then ErrorMsg("Cannot fit logarithmic data to non-positive data") : Return False
                            X = Math.Log(X)
                            Y = Math.Log(Y)
                        Case enumCurveFit.Exponential
                            If Y <= 0 Then ErrorMsg("Cannot fit logarithmic data to non-positive data") : Return False
                            Y = Math.Log(Y)
                    End Select
                    SumX += X
                    SumX2 += X ^ 2
                    SumY += Y
                    SumY2 += Y ^ 2
                    SumXY += X * Y
                    Application.DoEvents()
                Next i

                If .NumPoints > 1 And .YMax - .YMin > 0 And .XMax - .XMin > 0 Then
                    .Slope = (SumXY - SumX * SumY / .NumPoints) / (SumX2 - SumX ^ 2 / .NumPoints)
                    .Intercept = (SumY - .Slope * SumX) / .NumPoints
                    Dim XBar As Double = SumX / .NumPoints
                    Dim YBar As Double = SumY / .NumPoints
                    SumX2 = 0
                    SumY2 = 0
                    SumXY = 0
                    For i As Integer = 0 To .NumPoints - 1
                        Dim X As Double = SourceCurve.Points(i).X
                        Dim Y As Double = SourceCurve.Points(i).Y
                        Select Case FitType
                            Case enumCurveFit.Linear, enumCurveFit.OneToOne
                            Case enumCurveFit.Power
                                X = Math.Log(X)
                                Y = Math.Log(Y)
                            Case enumCurveFit.Exponential
                                Y = Math.Log(Y)
                        End Select
                        SumX2 += (X - XBar) ^ 2
                        SumY2 += (Y - YBar) ^ 2
                        SumXY += (X - XBar) * (Y - YBar)
                    Next i
                    .CorrCoef = Math.Abs(SumXY / System.Math.Sqrt(SumX2 * SumY2))
                    Return True
                Else
                    Return False
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
            Return False
        End Try
    End Function

    Shared Sub ComputeProbFit(ByVal FitType As enumProbFit, ByVal ProbOptions As enumProbOption, ByRef basecrv As ZedGraph.LineItem, ByRef newcrv As ZedGraph.LineItem, ByRef FitStats As String)
        Try
            Dim Stats As structProbStats
            ComputeProbStats(basecrv, FitType, Stats)

            With Stats
                Dim zmin As Double = NormalInv(1.0 / (basecrv.NPts + 1))
                Dim zmax As Double = NormalInv(basecrv.NPts / (basecrv.NPts + 1))
                For z As Double = zmin To zmax Step (zmax - zmin) / 100
                    Dim Prob As Double = Normal(z)
                    If ProbOptions = enumProbOption.GreaterThan_Decr Or ProbOptions = enumProbOption.GreaterThan_Incr Then Prob = 1 - Prob
                    Select Case FitType
                        Case enumProbFit.Normal : newcrv.AddPoint(Prob, .Mean + z * .StdDev)
                        Case enumProbFit.LogNormal : newcrv.AddPoint(Prob, 10 ^ (.MeanLog + z * .StdDevLog))
                        Case enumProbFit.LogPearson3 : newcrv.AddPoint(Prob, 10 ^ (.MeanLog + LP3(Normal(z), .SkewLog) * .StdDevLog))
                    End Select
                    Application.DoEvents()
                Next

                'when used to plot low-flow analysis, compute nQ10 (global variable) and add to graph note

                Dim nQ10 As Double 'computed in GetData for series for probability plots, and placed in graph notes
                Dim zz As Double = NormalInv(0.1)
                If ProbOptions = enumProbOption.GreaterThan_Decr Or ProbOptions = enumProbOption.GreaterThan_Incr Then zz *= -1
                Select Case FitType
                    Case enumProbFit.Normal : nQ10 = .Mean + zz * .StdDev
                    Case enumProbFit.LogNormal : nQ10 = 10 ^ (.MeanLog + zz * .StdDevLog)
                    Case enumProbFit.LogPearson3 : nQ10 = 10 ^ (.MeanLog + LP3(Normal(zz), .SkewLog) * .StdDevLog)
                End Select
                'strip everything after last equals sign
                If TestNull(ActiveGraphTab(0).GraphOptions.NoteText, "").Contains("Q10 =") Then
                    Dim j As Integer = ActiveGraphTab(0).GraphOptions.NoteText.LastIndexOf("Q10 =")
                    ActiveGraphTab(0).GraphOptions.NoteText = ActiveGraphTab(0).GraphOptions.NoteText.Substring(0, j + 5) & " " & nQ10.ToString("N2")
                End If
            End With

            With Stats
                If FitType = enumProbFit.Normal Then
                    FitStats = String.Format("Num Pts = {0}\nMean = {1:N3}\nStd Dev = {2:N3}", .NumPoints, .Mean, .StdDev)
                Else
                    FitStats = String.Format("Num Pts = {0}\nMean = {1:N3}\nStd Dev = {2:N3}\nSkew = {3:N3}\n(Stats are for logs)", .NumPoints, .MeanLog, .StdDevLog, .SkewLog)
                End If
                FitStats &= "\nProbability estimates:"
                For i As Integer = 1 To 9
                    Dim est As Double
                    Dim prob As Double = Choose(i, 0.01, 0.02, 0.05, 0.1, 0.5, 0.9, 0.95, 0.98, 0.99)
                    Dim zz As Double = NormalInv(prob)
                    If ProbOptions = enumProbOption.GreaterThan_Decr Or ProbOptions = enumProbOption.GreaterThan_Incr Then zz *= -1
                    Select Case FitType
                        Case enumProbFit.Normal : est = .Mean + zz * .StdDev
                        Case enumProbFit.LogNormal : est = 10 ^ (.MeanLog + zz * .StdDevLog)
                        Case enumProbFit.LogPearson3 : est = 10 ^ (.MeanLog + LP3(Normal(zz), .SkewLog) * .StdDevLog)
                    End Select
                    FitStats &= String.Format("\n{0}%: {1:N2}", prob * 100, est)
                Next
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Shared Sub ComputeProbStats(ByVal SourceCurve As ZedGraph.LineItem, ByVal FitType As enumProbFit, ByRef Stats As structProbStats)
        Try
            Dim SumY As Double = 0, SumY2 As Double = 0, SumY3 As Double = 0, gSumY As Double = 0, gSumY2 As Double = 0, gSumY3 As Double = 0
            Dim YMin As Double = Double.MaxValue, YMax As Double = Double.MinValue

            'todo: handle nondetect, etc.

            For i As Integer = 0 To SourceCurve.NPts - 1
                Dim Y As Double = SourceCurve.Points(i).Y
                YMin = Math.Min(YMin, Y) 'may be less than y data in Result since multiplied by detection limit
                YMax = Math.Max(YMax, Y)
                SumY = SumY + Y
                SumY2 = SumY2 + Y * Y
                SumY3 = SumY3 + Y * Y * Y
                If FitType <> enumProbFit.Normal Then
                    gSumY = gSumY + Math.Log10(Y + 0.0001)
                    gSumY2 = gSumY2 + Math.Log10(Y + 0.0001) ^ 2
                    gSumY3 = gSumY3 + Math.Log10(Y + 0.0001) ^ 3
                End If
                Application.DoEvents()
            Next i

            With Stats
                .NumPoints = SourceCurve.NPts
                .Mean = SumY / .NumPoints
                If .NumPoints > 1 And YMax <> YMin Then
                    .StdDev = Math.Sqrt((.NumPoints * SumY2 - SumY ^ 2) / (.NumPoints * (.NumPoints - 1)))
                Else
                    .StdDev = 0
                End If
                If .NumPoints > 2 And .StdDev <> 0 Then
                    .Skew = (.NumPoints ^ 2 * SumY3 - 3.0# * .NumPoints * SumY * SumY2 + 2.0# * SumY ^ 3) / (CDbl(.NumPoints) * (.NumPoints - 1) * (.NumPoints - 2) * .StdDev ^ 3)
                Else
                    .Skew = 0
                End If
                If FitType <> enumProbFit.Normal Then
                    .MeanLog = gSumY / .NumPoints
                    If .NumPoints <= 1 Then
                        .StdDevLog = 1
                    Else
                        .StdDevLog = Math.Sqrt((.NumPoints * gSumY2 - gSumY ^ 2) / (.NumPoints * (.NumPoints - 1)))
                    End If
                    If .NumPoints > 2 Then
                        .SkewLog = (.NumPoints ^ 2 * gSumY3 - 3.0# * .NumPoints * gSumY * gSumY2 + 2.0# * gSumY ^ 3) / (CDbl(.NumPoints) * (.NumPoints - 1) * (.NumPoints - 2) * .StdDevLog ^ 3)
                    Else
                        .SkewLog = 0
                    End If
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub
#End Region

    Private Sub dgSeries_CellValidated(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles dgSeries.CellValidated
        ActiveGraphTab.Zed.Refresh()
    End Sub

    Private Sub dgSeries_CurrentCellDirtyStateChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles dgSeries.CurrentCellDirtyStateChanged
        Try
            With dgSeries
                If .IsCurrentCellDirty Then
                    .SuspendLayout()
                    .CommitEdit(DataGridViewDataErrorContexts.Commit)
                    SaveGrid()
                    'reload grid in case default series name adopted (by clearing the entry)
                    Dim c As DataGridViewCell = .CurrentCell
                    lstFiles.Items.Clear()
                    For Each tbl As clsTable In OpenTables
                        lstFiles.Items.Add(tbl.ToString)
                    Next
                    .CurrentCell = c
                    .ResumeLayout()
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub SaveGrid()
        Try
            Dim row As Integer = dgSeries.CurrentCellAddress.Y
            Dim col As enumCol = dgSeries.CurrentCellAddress.X
            Dim v As Object = dgSeries.CurrentCell.FormattedValue
            If row < 0 Then Exit Sub
            Dim ser As clsSeries = ActiveGraphTab.Series(row)
            With ser
                Select Case col

                    Case enumCol.StaIX
                        With CType(ser, clsSeriesScatter)
                            .StationIDX = v
                            .PCodeListX = .GetPCodeList(.StationIDX)
                            dgvSeries.SetCellFormatList(enumCol.PCodX, row, .PCodeListX)

                            'make sure existing pcode is contained in new pcode list; if not will reset to first one
                            Dim lstPCodes As New Generic.List(Of String)
                            lstPCodes.AddRange(.PCodeListX.Split(";"))
                            If Not lstPCodes.Contains(.PCodeX) Then
                                .PCodeX = lstPCodes(0)
                                dgSeries.Item(enumCol.PCodX, row).Value = .PCodeX
                            End If
                        End With

                    Case enumCol.PCodX
                        With CType(ser, clsSeriesScatter)
                            .PCodeX = v
                        End With

                    Case enumCol.StaID
                        .StationID = v
                        .PCodeList = .GetPCodeList(.StationID)
                        dgvSeries.SetCellFormatList(enumCol.PCode, row, .PCodeList)

                        'make sure existing pcode is contained in new pcode list; if not will reset to first one
                        Dim lstPCodes As New Generic.List(Of String)
                        lstPCodes.AddRange(.PCodeList.Split(";"))
                        If Not lstPCodes.Contains(.PCode) Then
                            .PCode = lstPCodes(0)
                            dgSeries.Item(enumCol.PCode, row).Value = .PCode
                        End If

                        If ActiveGraphTab.GraphType = enumGraphType.Depth Then
                            .DateList = .GetDateList(.StationID, .PCode)
                            dgvSeries.SetCellFormatList(enumCol.Dates, row, .DateList)
                            'make sure existing date-time is contained in new list; if not will reset to first one
                            Dim lstDates As New Generic.List(Of String)
                            lstDates.AddRange(.DateList.Split(";"))
                            If Not lstDates.Contains(.DateTime) Then
                                .DateTime = lstDates(0)
                                dgSeries.Item(enumCol.Dates, row).Value = .DateTime
                            End If

                        End If


                    Case enumCol.PCode
                        .PCode = v
                        If dgSeries.Columns(enumCol.Dates).Visible Then
                            .DateList = ser.GetDateList(.StationID, .PCode)
                            dgvSeries.SetCellFormatList(enumCol.Dates, row, .DateList)
                            'make sure existing datetime is contained in new datelist; if not will reset to first one
                            Dim lstDates As New Generic.List(Of String)
                            lstDates.AddRange(.DateList.Split(";"))
                            If Not lstDates.Contains(.DateTime) Then
                                .PCode = lstDates(0)
                                dgSeries.Item(enumCol.Dates, row).Value = .DateTime
                            End If
                        End If

                    Case enumCol.Dates : .DateTime = v

                    Case enumCol.TmWin
                        If v <> "All" And v <> "None" And v <> "BMD" Then v = "_" & CStr(v).Replace(" ", "_")
                        .TimeWindow = [Enum].Parse(GetType(enumTimeWindow), v)

                    Case enumCol.TblNm
                        .ReplaceTable(OpenTables(lstFiles.Items.IndexOf(v)))

                        Dim lst As New Generic.List(Of String)
                        lst.AddRange(.StationList.Split(";"))

                        If GraphType = enumGraphType.Scatter Then
                            With CType(ser, clsSeriesScatter)
                                dgvSeries.SetCellFormatList(enumCol.StaIX, row, .StationList)
                                dgSeries.Item(enumCol.StaIX, row).Value = .StationIDX
                                dgvSeries.SetCellFormatList(enumCol.PCodX, row, .PCodeListX)
                                dgSeries.Item(enumCol.PCodX, row).Value = .PCodeX
                            End With
                        End If
                        dgvSeries.SetCellFormatList(enumCol.StaID, row, .StationList)
                        dgSeries.Item(enumCol.StaID, row).Value = .StationID
                        dgvSeries.SetCellFormatList(enumCol.PCode, row, .PCodeList)
                        dgSeries.Item(enumCol.PCode, row).Value = .PCode
                        dgvSeries.SetCellFormatList(enumCol.Dates, row, .DateList)
                        dgSeries.Item(enumCol.Dates, row).Value = .DateTime
                        dgSeries.Item(enumCol.TmWin, row).Value = [Enum].GetName(GetType(enumTimeWindow), .TimeWindow)
                        With ActiveGraphTab()
                            .ResetYAxes()
                            .Zed.AxisChange()
                            .Zed.Refresh()
                            If .Series.Count = 1 Then ActiveGraphTab.ZoomExtents()
                        End With

                    Case enumCol.Visbl : .Visible = v

                    Case enumCol.XAxis : .XAxis = XAxisList.IndexOf(v)

                    Case enumCol.YAxis : .YAxis = YAxisList.IndexOf(v)

                    Case enumCol.YIndx : .YAxisIndex = IndexList.IndexOf(v)

                    Case enumCol.LColr : .LineColor = Color.FromName(v)

                    Case enumCol.LStyl : .LineType = StyleList.IndexOf(v) - 1

                    Case enumCol.LWdth : .LineWidth = v

                    Case enumCol.SType
                        .SymbolType = SymbolList.IndexOf(v) - 1
                        dgSeries.CurrentCell.Value = SymbolList(.SymbolType + 1) 'may get reset

                    Case enumCol.SSize : .SymbolSize = TestNull(v, 0)

                    Case enumCol.SFill : .SymbolFill = v

                    Case enumCol.Label : .Label = v

                End Select

                Select Case col
                    Case enumCol.StaIX, enumCol.PCodX, enumCol.StaID, enumCol.PCode, enumCol.Dates, enumCol.TmWin
                        If .IsCalc Then Exit Sub
                        .GetData()
                        .GetDefaultLabel()
                        ActiveGraphTab.SetGraphTitle()
                        ActiveGraphTab.ResetYAxes()
                        dgSeries.Item(enumCol.Label, row).Value = .Label
                        Select Case GraphType
                            Case enumGraphType.Depth, enumGraphType.Profile, enumGraphType.Width
                                If ActiveGraphTab.Series.Count = 1 Then
                                    ActiveGraphTab.SetSeriesDateTime(.DateTime, .TimeWindow)
                                    ActiveGraphTab.AnimationInfo.TimeWindow = .TimeWindow
                                    ActiveGraphTab.AnimationInfo.DateTime = .DateTime
                                    ActiveGraphTab.ResetProfileScales()
                                    GraphForm.ResetAnimationLimits()
                                End If
                            Case enumGraphType.Scatter
                                If ActiveGraphTab.Series.Count = 1 Then
                                    ActiveGraphTab.AnimationInfo.TimeWindow = .TimeWindow
                                End If
                        End Select
                    Case enumCol.XAxis, enumCol.YAxis, enumCol.YIndx
                        ActiveGraphTab.ResetYAxes()
                        If GraphType = enumGraphType.Profile Then ActiveGraphTab.ResetProfileScales()
                End Select
            End With
            Select Case col
                Case enumCol.StaIX, enumCol.PCodX, enumCol.StaID, enumCol.PCode, enumCol.Dates, enumCol.TmWin, enumCol.Visbl, enumCol.XAxis, enumCol.YAxis, enumCol.YIndx : ActiveGraphTab.Zed.AxisChange()
            End Select
            ActiveGraphTab.Zed.Refresh()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub dgSeries_RowsAdded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowsAddedEventArgs) Handles dgSeries.RowsAdded
        If dgSeries.Visible Then EnableSeriesButtons()
    End Sub

    Private Sub dgSeries_RowsRemoved(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewRowsRemovedEventArgs) Handles dgSeries.RowsRemoved
        If dgSeries.Visible Then EnableSeriesButtons()
    End Sub

    Private Sub dgSeries_SelectionChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles dgSeries.SelectionChanged
        If dgSeries.Visible Then EnableSeriesButtons()
    End Sub

    Private Sub EnableSeriesButtons()
        Try
            Dim Enabled As Boolean = dgSeries.RowCount > 0
            btnSeriesSelect.Enabled = Enabled
            btnSeriesRemove.Enabled = Enabled
            btnCalculate.Enabled = Enabled And GraphType <> enumGraphType.Histogram
            Dim RowSel As New Generic.List(Of Integer)
            For Each c As DataGridViewCell In dgSeries.SelectedCells
                If Not RowSel.Contains(c.RowIndex) Then RowSel.Add(c.RowIndex)
            Next
            mnuCalcConst.Enabled = RowSel.Count = 1
            mnuCalcVariable.Enabled = RowSel.Count = 1
            mnuCalcPCodeLimit.Enabled = RowSel.Count = 1
            mnuCalcDailyAvgMinMax.Enabled = RowSel.Count = 1
            mnuCalcMovingAvg.Enabled = RowSel.Count = 1
            mnuCalcSine.Enabled = RowSel.Count = 1
            mnuCalcAdjust.Enabled = RowSel.Count = 1
            mnuCalcDuplicate.Enabled = RowSel.Count = 1
            mnuCalcEdit.Enabled = RowSel.Count = 1
            mnuCalcAdd.Enabled = RowSel.Count >= 1
            mnuCalcSubtract.Enabled = RowSel.Count >= 1
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub frmSeries_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        SaveWindowPos(REGAPP, Me)
        SaveSetting(REGAPP, Me.Name, "SplitterDistance", SplitContainer1.SplitterDistance)
    End Sub

    Private Sub frmSeries_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Try
            GetWindowPos(REGAPP, Me)
            SplitContainer1.SplitterDistance = GetSetting(REGAPP, Me.Name, "SplitterDistance", SplitContainer1.SplitterDistance)

            GraphType = ActiveGraphTab.GraphType

            lblRefreshing.Visible = True
            dgSeries.Visible = False
            Application.DoEvents()

            dgvSeries = New WRDB.Controls.DGVEditor(dgSeries)

            With dgvSeries

                Dim StationIDY As String = "Station ID", PCodeY As String = "PCode"
                Select Case GraphType
                    Case enumGraphType.Profile : StationIDY = "Branch ID"
                    Case enumGraphType.Scatter : StationIDY = "Station ID Y" : PCodeY = "PCode Y"
                End Select

                .SetHeadings(String.Format("Station ID X~PCode X~{0}~{1}~Date-Time~Time Win~Table Name~Show?~X Axis~Y Axis~Axis #~Color~Line~Width~Symbol~Size~Fill?~Series Label", StationIDY, PCodeY), False, 0)
                .AllowRowChange = False
                .DataGridView.RowHeadersVisible = True
                .DataGridView.ShowRowErrors = False
                .DataGridView.Columns(enumCol.Label).ValueType = GetType(String)
                .DataGridView.Columns(enumCol.TblNm).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft
                .DataGridView.Columns(enumCol.Label).DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.StaIX)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.PCodX)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.StaID)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.PCode)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.Dates)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.TmWin)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.TblNm)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.CheckBox, enumCol.Visbl)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.XAxis, XAxisList)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.YAxis, YAxisList)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.YIndx, IndexList)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ColorPicker, enumCol.LColr)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.LStyl, StyleList)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.LWdth, WidthList)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.SType, SymbolList)
                .SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.SSize, SizeList)
                'set the tooltips
                Dim ToolTipText() As String = _
                {"The station identifier for the X axis of the scatter plot.", _
                 "The parameter code for the X axis of the scatter plot.", _
                 IIf(GraphType = enumGraphType.Profile, "The branch number for the profile series.", "The station identifier or name for the time series or depth profile graph."), _
                 "The parameter code associated with the series.", _
                 "The date & time from which the profile data are taken.", _
                 "The size of the time window.", _
                 "This column shows the data table or file from which the data were taken.", _
                 "If checked, the series is visible; you can also control series\nvisiblility by right-clicking on a series data point on the graph.", _
                 "Select whether you want the series plotted against the bottom or top x axis.", _
                 "Select whether you want the series plotted against the left or right y axis.", _
                 "Select which of to up to five y axes on each of the right and left the series\nshould be assigned to; when the series is initially created, the axis number can be\nautomatically selected so that previously added series axes are reused.", _
                 "This is the line and symbol color associated with the series;\nby default unique colors are assigned to new series.", _
                 "This is the line type associated with the series; by default\nnew series are assigned solid line types.", _
                 "This is the line width associated with the series; by default\nnew series are assigned 0 widths.", _
                 "This is the type of symbol associated with the series; by\ndefault unique symbols are assigned to new series.", _
                 "This is the size of the symbol associated with the series; by default\nnew series are assigned a size of 6.", _
                 "Is the symbol filled?", _
                 "The label that appears in the legend, if visible; when the series is\ninitially created, it defaults to the Station ID and PCode."}
                With .DataGridView
                    For c As Integer = 0 To .ColumnCount - 1
                        .Columns(c).HeaderCell.ToolTipText = ToolTipText(c).Replace("\n", vbCr)
                    Next
                    .Columns(enumCol.StaIX).Frozen = True
                    .Columns(enumCol.PCodX).Frozen = True
                    .Columns(enumCol.StaID).Frozen = True
                    .Columns(enumCol.PCode).Frozen = True
                    .Columns(enumCol.Dates).Frozen = True
                    .Columns(enumCol.TmWin).Frozen = True
                End With
            End With

            'disable most buttons until something is selected
            btnFileClose.Enabled = False
            btnStations.Enabled = False
            btnPCodes.Enabled = False
            btnAddLeft.Enabled = False
            btnAddRight.Enabled = False
            btnSeriesSelect.Enabled = False
            btnSeriesRemove.Enabled = False
            btnCalculate.Enabled = False

            lstStations.Enabled = GraphType <> enumGraphType.Profile
            btnStations.Enabled = GraphType <> enumGraphType.Profile

            lstFiles.Items.Clear()
            For Each tbl As clsTable In OpenTables
                lstFiles.Items.Add(tbl.ToString)
            Next
            If lstFiles.Items.Count > 0 Then lstFiles.SelectedIndex = 0

            'if this is first time was called during the session, all the lists were filled using SQL queries; save these lists in case open again
            With GraphForm.SeriesLists
                .TableName = lstFiles.Text
                ReDim .Branches(cboBranch.Items.Count - 1)
                cboBranch.Items.CopyTo(.Branches, 0)
                ReDim .Stations(lstStations.Items.Count - 1)
                lstStations.Items.CopyTo(.Stations, 0)
                ReDim .PCodes(lstPCodes.Items.Count - 1)
                lstPCodes.Items.CopyTo(.PCodes, 0)
            End With

            'add existing series to the grid
            'frmGraph has current active tab and it's Tag is set to an instance of clsGraphTab which contains a list of series

            dgSeries.Rows.Clear()

            For Each ser As clsSeries In ActiveGraphTab.Series
                AddSeriesToGrid(ser)
            Next

            'after all series loaded, set autoscale mode on appropriate columns
            With dgvSeries
                For c As enumCol = 0 To .DataGridView.ColumnCount - 1
                    Select Case c
                        Case enumCol.Dates : .SetColumnWidth(c, "XX/XX/XXXX XX:XX_X")
                        Case enumCol.TmWin : .SetColumnWidth(c, "Time WinXXX")
                        Case enumCol.XAxis : .SetColumnWidth(c, "BottomXXX")
                        Case enumCol.YAxis : .SetColumnWidth(c, "RightXXXX")
                        Case enumCol.LColr : .SetColumnWidth(c, "DarkMagentaXXX")
                        Case enumCol.LStyl : .SetColumnWidth(c, "XXXXXXXXXX")
                        Case enumCol.SType : .SetColumnWidth(c, "DiamondXXXX")
                        Case Else
                            .DataGridView.Columns(c).AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells
                    End Select
                Next
            End With
            With dgSeries
                .Columns(enumCol.StaIX).Visible = False
                .Columns(enumCol.PCodX).Visible = False
                .Columns(enumCol.Dates).Visible = False
                .Columns(enumCol.TmWin).Visible = False
                Select Case GraphType
                    Case enumGraphType.Scatter
                        .Columns(enumCol.StaIX).Visible = True
                        .Columns(enumCol.PCodX).Visible = True
                        .Columns(enumCol.TmWin).Visible = True
                    Case enumGraphType.Depth, enumGraphType.Profile, enumGraphType.Width
                        .Columns(enumCol.Dates).Visible = True
                        .Columns(enumCol.TmWin).Visible = True
                End Select
                If dgSeries.Rows.Count > 0 Then .CurrentCell = dgSeries.Item(enumCol.StaID, 0)
            End With

            UpdateTableList()
            EnableSeriesButtons()
            Application.DoEvents()

            cboGraphType.SelectedIndex = GraphType

            lblRefreshing.Visible = False
            dgSeries.Visible = True

            If lstFiles.Items.Count = 0 Or ShowOpen Then btnFileOpen.PerformClick()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' Selected table has changed; refresh the list of station IDs and PCodes
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub lstFiles_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles lstFiles.SelectedIndexChanged
        Try
            btnCriteria.Visible = False
            If lstFiles.SelectedIndex = -1 Then Exit Sub
            Cursor = Cursors.WaitCursor
            WriteProgress(Owner, "Updating display...")
            Dim starttime As DateTime = Now

            With OpenTables(lstFiles.SelectedIndex)
                btnCriteria.Visible = (.TableType = clsTable.enumTableType.WRDB5 Or .TableType = clsTable.enumTableType.WRDB6)
                cboBranch.Items.Clear()
                If lstFiles.Text = GraphForm.SeriesLists.TableName Then
                    cboBranch.Items.AddRange(GraphForm.SeriesLists.Branches)
                Else
                    cboBranch.Items.AddRange(.GetBranchList.ToArray)
                End If
                If cboBranch.Items.Count > 0 Then cboBranch.SelectedIndex = 0
                ToolTip1.SetToolTip(lstFiles, "")
                Select Case .TableType
                    Case clsTable.enumTableType.BMD, clsTable.enumTableType.BMD2, clsTable.enumTableType.Folder, clsTable.enumTableType.WRDBData
                        ToolTip1.SetToolTip(lstFiles, "Currently selected table path:" & vbCr & IIf(.IsConvertedDB, IO.Path.GetDirectoryName(.OriginalDBName), .DataSource))
                End Select
                If (.TableType = clsTable.enumTableType.WRDB4 Or
                    .TableType = clsTable.enumTableType.WRDB5 Or
                    .TableType = clsTable.enumTableType.WRDB6) And
                    Now.Subtract(starttime).Seconds > 15 Then
                    frmInfoMsg.ShowInfoMsg("Query Data Table Contents", "It took a long time to extract unique Station IDs and PCodes for this data table. With WRDB tables it will be helpful to add indices on at least these two fields using File | Explore Database | Manage Indices.")
                End If
            End With
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            btnFileClose.Enabled = lstFiles.Items.Count > 0
            WriteProgress(Owner)
            Cursor = Cursors.Default
        End Try
    End Sub

    Private Sub lstPCodes_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles lstPCodes.DoubleClick
        btnAddLeft.PerformClick()
    End Sub

    Private Sub lstPCodes_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles lstPCodes.SelectedIndexChanged
        lblSeries.Text = String.Format("{0} Series Selected", IIf(GraphType = enumGraphType.Profile, IIf(cboBranch.SelectedIndex = 0, Math.Max(1, cboBranch.Items.Count - 1), 1), lstStations.SelectedItems.Count) * lstPCodes.SelectedItems.Count)
        Dim Enabled As Boolean = lstPCodes.Items.Count > 0 And lstPCodes.SelectedIndex <> -1
        btnPCodes.Enabled = Enabled
        btnAddLeft.Enabled = Enabled
        btnAddRight.Enabled = Enabled
    End Sub

    Private Sub lstStations_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles lstStations.SelectedIndexChanged
        Try
            If lstFiles.SelectedIndex = -1 OrElse lstStations.SelectedIndex = -1 OrElse lstStations.Tag = "Busy" Then Exit Sub
            txtPCodes.Clear()
            FillPCodesList()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub FillPCodesList()
        Dim lastPCode As String = lstPCodes.Text
        lstPCodes.BeginUpdate()
        lstPCodes.Items.Clear()
        'disabled the following 3/11/16
        If False And lstFiles.Text = GraphForm.SeriesLists.TableName And cboBranch.SelectedIndex = 0 And lstStations.SelectedIndex = 0 And lstStations.SelectedItems.Count = 1 Then
            For Each s As String In GraphForm.SeriesLists.PCodes
                If s.ToUpper Like "*" & txtPCodes.Text.ToUpper & "*" Then lstPCodes.Items.Add(s)
            Next
        Else
            Dim lst As New Generic.List(Of String)
            For Each s As String In lstStations.SelectedItems
                lst.Add(StringSplit(s, " - ")(0))
            Next
            For Each s As String In OpenTables(lstFiles.SelectedIndex).GetPCodeList(lst.ToArray)
                If s.ToUpper Like "*" & txtPCodes.Text.ToUpper & "*" Then lstPCodes.Items.Add(s)
            Next
        End If
        If lstPCodes.Items.Count > 0 Then lstPCodes.SelectedIndex = 0
        lstPCodes.EndUpdate()
        For i As Integer = 0 To lstPCodes.Items.Count - 1
            If lstPCodes.Items(i) = lastPCode Then
                lstPCodes.ClearSelected()
                lstPCodes.SelectedIndex = i
                Exit For
            End If
        Next
        lblSeries.Text = String.Format("{0} Series Selected", lstStations.SelectedItems.Count * lstPCodes.SelectedItems.Count)
        btnPCodes.Enabled = lstPCodes.Items.Count > 0
    End Sub

    Private Sub mnuOpenFile_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles mnuOpenFile.Click, mnuOpenTable.Click
        SaveSetting(REGAPP, "General", "LastOpenType", IIf(sender Is mnuOpenFile, "File", "Table"))
        btnFileOpen.PerformClick()
    End Sub

    Private Sub mnuCalc_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuCalcAdd.Click, mnuCalcAdjust.Click, mnuCalcConst.Click, mnuCalcDailyAvgMinMax.Click, mnuCalcMovingAvg.Click, mnuCalcSine.Click, mnuCalcSubtract.Click, mnuCalcScatterOneToOne.Click, mnuCalcScatterLinear.Click, mnuCalcScatterPower.Click, mnuCalcScatterExponential.Click, mnuCalcProbNormal.Click, mnuCalcProbLogNormal.Click, mnuCalcProbLogPearson.Click, mnuCalcPCodeLimit.Click, mnuCalcVariable.Click, mnuCalcDuplicate.Click, mnuCalcEdit.Click
        Try
            With ActiveGraphTab()
                Dim ser As clsSeries
                ser = .Series(dgSeries.CurrentCellAddress.Y)
                If sender Is mnuCalcEdit Then
                    If Not ser.IsCalc Then
                        WarningMsg("You cannot edit unless you select a calculated series.")
                        Exit Sub
                    End If
                Else
                    If ser.IsCalc Then
                        WarningMsg("You cannot create new calculated series from other calculated series.")
                        Exit Sub
                    End If
                End If
                Dim zc As New ZedGraph.LineItem(ser.Label, New ZedGraph.PointPairList, Color.Fuchsia, SymbolType.None, 1)
                Dim FitStats As String = ""
                zc.IsVisible = ser.ZedCurve.IsVisible
                zc.IsX2Axis = ser.ZedCurve.IsX2Axis
                zc.IsY2Axis = ser.ZedCurve.IsY2Axis
                zc.YAxisIndex = ser.ZedCurve.YAxisIndex

                If sender Is mnuCalcAdd Or sender Is mnuCalcSubtract Then
                    Dim ser2 As clsSeries = Nothing
                    For i As Integer = 0 To dgSeries.SelectedCells.Count - 1
                        If dgSeries.SelectedCells(i).RowIndex <> dgSeries.CurrentCellAddress.Y Then
                            ser2 = .Series(dgSeries.SelectedCells(i).RowIndex)
                            Exit For
                        End If
                    Next
                    With New frmSelect2Series
                        .Text = String.Format("{0} Selected Series", IIf(sender Is mnuCalcAdd, "Add", "Subtract"))
                        .lblInfo.Text = StringFormat("A new series will be computed, comprised of two series you select above. If no data point is found in the second series at the same time/place as a point in the first, a value will be linearly interpolated in the second for comparison.\n\nFor example, if you are comparing two BMD series or two WRDB table series, you can select the series in any order, and the computed differences will be equal to the values in the first series {1} coincident (or interpolated) values in the second series.\n\nIf you are comparing a BMD series and WRDB table series, select the WRDB table series first and then the BMD series in order to have the differences computed in a logical manner.", IIf(sender Is mnuCalcAdd, "sum of", "difference between"), IIf(sender Is mnuCalcAdd, "plus", "minus")).Replace(vbCr, vbCrLf)
                        For Each r As DataGridViewRow In dgSeries.Rows
                            Dim seriesname As String = r.Cells(enumCol.Label).Value
                            .lstSeries1.Items.Add(seriesname)
                            .lstSeries2.Items.Add(seriesname)
                        Next
                        'If ser IsNot Nothing Then .lstSeries1.Text = ser.Label
                        'If ser2 IsNot Nothing Then .lstSeries2.Text = ser2.Label
                        If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                            ser = ActiveGraphTab.Series(.lstSeries1.SelectedIndex)
                            ser2 = ActiveGraphTab.Series(.lstSeries2.SelectedIndex)
                            ComputeAdd_Subtract(sender Is mnuCalcAdd, ser.ZedCurve, ser2.ZedCurve, zc, FitStats)
                            zc.Label.Text = ser.Label & IIf(sender Is mnuCalcAdd, " + ", " - ") & ser2.Label
                            .Dispose()
                        Else
                            .Dispose()
                            Exit Sub
                        End If
                    End With

                ElseIf sender Is mnuCalcAdjust Then
                    Dim s As String = ""
                    Do
                        s = InputBox(String.Format("Enter arithmetic operator (one of +, -, *, or /) and then constant value to compute new calculated series to be associated with {0}:", ser.Label), "Adjust by Constant Value", s)
                        s = s.Trim
                        If s.Trim = "" Then Exit Sub
                        If s.Length >= 2 AndAlso "+-*/".Contains(s.Substring(0, 1)) AndAlso IsNumeric(s.Substring(1)) Then Exit Do
                        WarningMsg("You must enter the arithmetic operator as the first character, and then the constant value to apply.")
                    Loop
                    ComputeAdjust(s, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Adjusted " & s

                ElseIf sender Is mnuCalcDuplicate Then
                    ComputeAdjust("+0", ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Duplicate "

                ElseIf sender Is mnuCalcConst Then
                    Dim s As String = InputBox(String.Format("Enter constant value for new calculated series to be associated with {0}:", ser.Label), "Calculate Constant Value")
                    If Not IsNumeric(s) Then Exit Sub
                    ComputeConstant(Val(s), ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Constant=" & Val(s)

                ElseIf sender Is mnuCalcEdit Or sender Is mnuCalcVariable Then
                    Dim dt As New DataTable
                    dt.Columns.Add("X Values", IIf(ActiveGraphTab.GraphType = enumGraphType.TimeSeries, GetType(DateTime), GetType(Double)))
                    dt.Columns.Add("Y Values", GetType(Double))
                    If sender Is mnuCalcEdit Then 'fill grid with points from referenced series
                        For i As Integer = 0 To ser.ZedCurve.NPts - 1
                            With ser.ZedCurve.Points(i)
                                Dim X As Object = .X
                                If ActiveGraphTab.GraphType = enumGraphType.TimeSeries Then
                                    X = DateTime.FromOADate(.X)
                                End If
                                dt.Rows.Add(X, .Y)
                            End With
                        Next
                    Else 'add 100 empty rows
                        For i As Integer = 0 To 99
                            dt.Rows.Add()
                        Next
                    End If

                    With New frmTable
                        .SetDataTable(dt)
                        If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                            If sender Is mnuCalcEdit Then
                                For i As Integer = 0 To ser.ZedCurve.NPts - 1
                                    ser.ZedCurve.RemovePoint(0)
                                Next
                                For Each r As DataRow In dt.Rows
                                    If Not IsDBNull(r(0)) AndAlso Not IsDBNull(r(1)) Then
                                        If ActiveGraphTab.GraphType = enumGraphType.TimeSeries Then
                                            ser.ZedCurve.AddPoint(CDate(r(0)).ToOADate, r(1))
                                        Else
                                            ser.ZedCurve.AddPoint(r(0), r(1))
                                        End If
                                    End If
                                Next
                                ActiveGraphTab.Zed.AxisChange()
                                ActiveGraphTab.Zed.Refresh()
                                Exit Sub
                            Else
                                ComputeVariable(dt, ser.ZedCurve, zc, FitStats)
                                zc.Label.Text &= " - Variable"
                            End If
                        Else
                            Exit Sub
                        End If
                        .Dispose()
                    End With

                ElseIf sender Is mnuCalcPCodeLimit Then
                    Dim minlimit As Double = Double.MinValue
                    Dim maxlimit As Double = Double.MaxValue
                    If ser.Table.TableType = clsTable.enumTableType.WRDB4 Or ser.Table.TableType = clsTable.enumTableType.WRDB5 Or ser.Table.TableType = clsTable.enumTableType.WRDB6 Then
                        Dim Criteria As New WRDB.Project.clsCriteria(ser.Table.WRDBProject, ser.Table.TableName)
                        ComputeLimits(ActiveGraphTab.GraphType, Criteria, ser.StationID, ser.PCode, ser.DateTime, ser.ZedCurve, zc, FitStats)
                        Criteria.Dispose()
                    ElseIf ser.Table.TableType = clsTable.enumTableType.Calibration Then
                        Select Case ser.Table.Calibration.tblMeasured.TableType
                            Case clsTable.enumTableType.WRDB4, clsTable.enumTableType.WRDB5, clsTable.enumTableType.WRDB6
                                Dim Criteria As New WRDB.Project.clsCriteria(ser.Table.Calibration.tblMeasured.WRDBProject, ser.Table.Calibration.tblMeasured.TableName)
                                ComputeLimits(ActiveGraphTab.GraphType, Criteria, ser.Table.Calibration.GetSourceStationID(ser.StationID, ser.PCode), ser.Table.Calibration.GetSourcePCode(ser.StationID, ser.PCode), ser.DateTime, ser.ZedCurve, zc, FitStats)
                                Criteria.Dispose()
                        End Select
                    Else
                        If ser.Table.PCodeSupport.ContainsKey(ser.PCode) Then
                            minlimit = ser.Table.PCodeSupport(ser.PCode).MinCriteria
                            maxlimit = ser.Table.PCodeSupport(ser.PCode).MaxCriteria
                            ComputeLimits(minlimit, maxlimit, ser.ZedCurve, zc, FitStats)
                        End If
                        If minlimit = Double.MinValue And maxlimit = Double.MaxValue Then
                            WarningMsg("No criteria limits have been set for PCode " & ser.PCode)
                            Exit Sub
                        End If
                    End If
                    zc.Label.Text = ser.PCode & " - Criteria Limits"

                ElseIf sender Is mnuCalcDailyAvgMinMax Then
                    'Dim time As String = InputBox("This will compute average daily values based on data found within each day and assign them to a specific time of day (HH:MM):", "Calculate Daily Averages", "00:00")
                    'If time.Trim = "" Then Exit Sub
                    With New frmCalcAvgMinMax
                        If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                            Dim time As String = .txtTime.Text
                            If Not IsDate(time) Then
                                WarningMsg("You must enter a valid time!")
                                Exit Sub
                            End If
                            Dim AvgMinMax As enumAvgMinMax = enumAvgMinMax.Avg
                            If .optMin.Checked Then AvgMinMax = enumAvgMinMax.Min
                            If .optMax.Checked Then AvgMinMax = enumAvgMinMax.Max
                            ComputeDailyAvgMinMax(ser.ZedCurve, zc, FitStats, AvgMinMax, time)
                            zc.Label.Text &= " - Daily " & Choose(AvgMinMax + 1, "Avg", "Min", "Max")
                        End If
                    End With

                ElseIf sender Is mnuCalcMovingAvg Then
                    Dim s As String = InputBox(String.Format("Enter the number of periods in the moving average to be associated with {0}:", ser.Label), "Moving Average Curve Fit", 7)
                    If Not IsNumeric(s) Then Exit Sub
                    If Val(s) < 2 Then ErrorMsg("You must enter an integer number of periods greater than 1.") : Exit Sub
                    ComputeMovingAvg(Val(s), ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Moving Avg=" & Val(s)

                ElseIf sender Is mnuCalcSine Then
                    Dim s As String = InputBox(String.Format("Enter the sine curve period in days (e.g., 365 computes an annual cycle) to be associated with {0}:", ser.Label), "Sine Curve Fit", 365)
                    If Not IsNumeric(s) Then Exit Sub
                    'If Val(s) < 3 Then ErrorMsg("You must enter an integer number of days greater than 2.") : Exit Sub
                    Dim _SineCurve As structSineCurve = Nothing
                    _SineCurve.Period = Val(s)
                    ComputeSineCurve(_SineCurve, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Sine Fit=" & _SineCurve.Period

                ElseIf sender Is mnuCalcScatterOneToOne Then
                    ComputeScatterFit(enumCurveFit.OneToOne, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - 1:1 Line"

                ElseIf sender Is mnuCalcScatterLinear Then
                    ComputeScatterFit(enumCurveFit.Linear, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Linear Fit"

                ElseIf sender Is mnuCalcScatterPower Then
                    ComputeScatterFit(enumCurveFit.Power, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Power Fit"

                ElseIf sender Is mnuCalcScatterExponential Then
                    ComputeScatterFit(enumCurveFit.Exponential, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Exponential Fit"

                ElseIf sender Is mnuCalcProbNormal Then
                    ComputeProbFit(enumProbFit.Normal, CType(ser, clsSeriesProbability).ProbOption, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Normal Fit"

                ElseIf sender Is mnuCalcProbLogNormal Then
                    ComputeProbFit(enumProbFit.LogNormal, CType(ser, clsSeriesProbability).ProbOption, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Log-Normal Fit"

                ElseIf sender Is mnuCalcProbLogPearson Then
                    ComputeProbFit(enumProbFit.LogPearson3, CType(ser, clsSeriesProbability).ProbOption, ser.ZedCurve, zc, FitStats)
                    zc.Label.Text &= " - Log-Pearson III Fit"

                End If

                .AddSeries(ser, zc, FitStats)

                .Zed.AxisChange()
                .Zed.Refresh()
                EnableSeriesButtons()
            End With

            'refresh grid so calced series appear

            Dim sel As Integer = dgSeries.CurrentCellAddress.Y
            dgSeries.Rows.Clear()
            dgSeries.Visible = False
            ActiveGraphTab.Zed.Visible = False
            For Each ser As clsSeries In ActiveGraphTab.Series
                AddSeriesToGrid(ser)
            Next
            dgSeries.Visible = True
            ActiveGraphTab.Zed.Visible = True
            dgSeries.CurrentCell = dgSeries.Item(enumCol.StaID, sel + 1)
            dgSeries.HorizontalScrollingOffset = 0
            UpdateTableList()
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    ''' <summary>
    ''' User clicked down arrow on button; add series to left/right depending on menu item selected
    ''' </summary>
    ''' <param name="sender"></param>
    ''' <param name="e"></param>
    ''' <remarks></remarks>
    Private Sub mnuAddExisting_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuAddExisting.Click, mnuAddCreate.Click
        Dim btn As Button = CType(CType(sender, ToolStripMenuItem).Owner, ContextMenuStrip).SourceControl 'the split button pressed to open context menu (will have name similar to btnXXX)
        AddSeries(btn Is btnAddLeft, sender Is mnuAddCreate)
    End Sub

    Private Sub UpdateTableList()
        Try
            Dim TableList As New Generic.List(Of String)
            For Each s As String In lstFiles.Items
                TableList.Add(s)
            Next

            dgvSeries.SetColumnFormat(WRDB.Controls.DGVEditor.enumColumnTypes.ComboBox, enumCol.TblNm, TableList)

            'now have to reset column contents
            For r As Integer = 0 To ActiveGraphTab.Series.Count - 1
                With ActiveGraphTab.Series(r)
                    If .IsCalc Then
                        Dim s As String = "Calculated: " & .Label
                        dgSeries.Item(enumCol.TblNm, r).Value = s
                        dgvSeries.SetCellFormatList(enumCol.TblNm, r, s)
                    Else
                        dgSeries.Item(enumCol.TblNm, r).Value = .Table.ToString
                    End If
                End With
            Next
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub dgvSeries_AfterContextMenu(ByVal sender As Object, ByVal e As ContextMenuEventArgs) Handles dgvSeries.AfterContextMenu
        Select Case e.ContextMenuType
            Case ContextMenuEventArgs.ContextMenuTypeEnum.Fill, ContextMenuEventArgs.ContextMenuTypeEnum.Add, ContextMenuEventArgs.ContextMenuTypeEnum.Paste
                For Each cell As DataGridViewCell In dgSeries.SelectedCells
                    dgSeries.CurrentCell = cell
                    SaveGrid()
                Next
        End Select
    End Sub

    Private Sub btnTop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTop.Click, btnUp.Click, btnDown.Click, btnBottom.Click
        Dim rownum As Integer = dgSeries.CurrentCellAddress.Y
        Dim newrownum As Integer
        With ActiveGraphTab.Series
            Dim ser As clsSeries = .Item(rownum)
            If sender Is btnTop Or sender Is btnUp Then
                newrownum = Math.Max(IIf(sender Is btnTop, 0, rownum - 1), 0)
            Else
                newrownum = Math.Min(IIf(sender Is btnBottom, .Count, rownum + 1), .Count)
            End If
            ActiveGraphTab.SetSeriesOrder(ser, newrownum)
        End With

        dgSeries.Rows.Clear()
        For Each ser As clsSeries In ActiveGraphTab.Series
            AddSeriesToGrid(ser)
        Next

        UpdateTableList()

        dgSeries.CurrentCell = dgSeries.Item(enumCol.StaID, Math.Min(newrownum, dgSeries.Rows.Count - 1))
        dgSeries.HorizontalScrollingOffset = 0

        With ActiveGraphTab()
            .SetGraphTitle()
            .ResetYAxes()
            .Zed.AxisChange()
            .Zed.Refresh()
        End With
    End Sub

    Private Sub mnuToolsBuildSingle_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolsBuildSingle.Click
        Close()
        Application.DoEvents()
        GraphForm.mnuToolsBuildSingle.PerformClick()
    End Sub

    Private Sub mnuToolsBuildMultiP_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolsBuildMultiP.Click
        Close()
        Application.DoEvents()
        GraphForm.mnuToolsBuildMultiP.PerformClick()
    End Sub

    Private Sub mnuToolsBuildMultiSP_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles mnuToolsBuildMultiSP.Click
        Close()
        Application.DoEvents()
        GraphForm.mnuToolsBuildMultiSP.PerformClick()
    End Sub

    Private Sub mnuToolsBuildCalib_Click(sender As Object, e As System.EventArgs) Handles mnuToolsBuildCalib.Click
        Close()
        Application.DoEvents()
        GraphForm.mnuToolsBuildCalib.PerformClick()
    End Sub

    Private Sub mnuBuildCalibrate_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsBuildCalibrate.Click
        GraphForm.mnuFileCalibration.PerformClick()
        lstFiles.Items.Clear()
        For Each tbl As clsTable In OpenTables
            lstFiles.Items.Add(tbl.ToString)
            If tbl.TableType = clsTable.enumTableType.Calibration Then lstFiles.Text = tbl.ToString
        Next
    End Sub

    Private Sub mnuToolsBuildPaste_Click(sender As System.Object, e As System.EventArgs) Handles mnuToolsBuildPaste.Click
        GraphForm.mnuToolsPaste.PerformClick()
        dgSeries.Rows.Clear()
        For Each ser As clsSeries In ActiveGraphTab.Series
            AddSeriesToGrid(ser)
        Next
    End Sub

    Private Sub mnuFiles_Opening(sender As System.Object, e As System.ComponentModel.CancelEventArgs) Handles mnuFiles.Opening
        If lstFiles.SelectedIndex <> -1 Then
            With OpenTables(lstFiles.SelectedIndex)
                If .TableType = clsTable.enumTableType.Calibration Then
                    mnuFilesClose.Visible = False
                    mnuFilesCalibManage.Visible = True
                    mnuFilesCalibTabs.Visible = True
                    mnuFilesCalibRpt.Visible = True
                Else
                    mnuFilesClose.Visible = True
                    mnuFilesCalibManage.Visible = False
                    mnuFilesCalibTabs.Visible = False
                    mnuFilesCalibRpt.Visible = False
                End If
            End With
        End If
    End Sub

    Private Sub mnuFilesClose_Click(sender As Object, e As System.EventArgs) Handles mnuFilesClose.Click
        btnClose.PerformClick()
    End Sub

    Private Sub mnuFilesManageCal_Click(sender As Object, e As System.EventArgs) Handles mnuFilesCalibManage.Click
        mnuToolsBuildCalibrate.PerformClick()
    End Sub

    Private Sub mnuFilesCalibTabs_Click(sender As Object, e As EventArgs) Handles mnuFilesCalibTabs.Click
        mnuToolsBuildCalib.PerformClick()
    End Sub

    Private Sub mnuFilesCalibRpt_Click(sender As Object, e As EventArgs) Handles mnuFilesCalibRpt.Click
        With New frmCalibrationMgr
            .Show(Me)
            .btnStats.PerformClick()
            .Close()
            .Dispose()
            If .HasChanged Then
                GraphForm.mnuViewRefresh.PerformClick()
                GraphForm._IsModified = True
            End If
        End With
    End Sub

    Private Sub btnCriteria_Click(sender As System.Object, e As System.EventArgs) Handles btnCriteria.Click
        Try
            With OpenTables(lstFiles.SelectedIndex)
                Dim frm As New frmSeriesCriteria
                Dim _MinDate As Date = Date.MinValue
                Dim _MaxDate As Date = Date.MaxValue
                Dim _MinDepth As Double = 0
                Dim _MaxDepth As Double = 9999
                Dim _MinWidth As Double = 0
                Dim _MaxWidth As Double = 9999
                Cursor = Cursors.WaitCursor
                Application.DoEvents()
                Dim dr As DbDataReader = .WRDBProject.DB.ExecuteReader("SELECT MIN(Date_Time),MAX(Date_Time),MIN(Depth_M),MAX(Depth_M),MIN(LEW_Pct),MAX(LEW_Pct) FROM {0}{1}", .TableName, .SelectedByClause("WHERE"))
                If dr IsNot Nothing And dr.Read Then
                    _MinDate = TestNull(dr.GetValue(0), Date.MinValue)
                    _MaxDate = TestNull(dr.GetValue(1), Date.MaxValue)
                    _MinDepth = TestNull(dr.GetValue(2), 0)
                    _MaxDepth = TestNull(dr.GetValue(3), 9999)
                    _MinWidth = TestNull(dr.GetValue(4), 0)
                    _MaxWidth = TestNull(dr.GetValue(5), 9999)
                End If
                dr.Close()
                If .WRDBProject.DB.ExecuteScalar("SELECT COUNT(*) FROM {0} WHERE Depth_M IS NULL{1}", .TableName, .SelectedByClause) > 0 Then _MinDepth = 0
                If .WRDBProject.DB.ExecuteScalar("SELECT COUNT(*) FROM {0} WHERE LEW_Pct IS NULL{1}", .TableName, .SelectedByClause) > 0 Then _MinWidth = 0
                For i As Integer = 1 To 4
                    Dim lbx As ListBox = Choose(i, frm.lstCCodes, frm.lstLCodes, frm.lstSCodes, frm.lstQCodes)
                    Dim fld As String = Choose(i, "CCode", "LCode", "SCode", "QCode")
                    lbx.Items.Add("<null>")
                    For j As Integer = 0 To lbx.Items.Count - 1
                        lbx.Items.AddRange(.WRDBProject.DB.GetRecordList(.TableName, fld).ToArray)
                    Next
                Next

                With MoreCriteria
                    If .MinDate <> Date.MinValue Then _MinDate = .MinDate
                    If .MaxDate <> Date.MaxValue Then _MaxDate = .MaxDate
                    If .MinDepth <> Double.MinValue Then _MinDepth = .MinDepth
                    If .MaxDepth <> Double.MaxValue Then _MaxDepth = .MaxDepth
                    If .MinWidth <> Double.MinValue Then _MinWidth = .MinWidth
                    If .MaxWidth <> Double.MaxValue Then _MaxWidth = .MaxWidth
                    For i As Integer = 1 To 4
                        Dim lbx As ListBox = Choose(i, frm.lstCCodes, frm.lstLCodes, frm.lstSCodes, frm.lstQCodes)
                        Dim lst As Generic.List(Of String) = Choose(i, .lstCCodes, .lstLCodes, .lstSCodes, .lstQCodes)
                        For j As Integer = 0 To lbx.Items.Count - 1
                            If lst.Contains(lbx.Items(j)) Then lbx.SetSelected(j, True)
                        Next
                    Next
                End With
                With frm
                    .dtStart.Value = _MinDate
                    .dtEnd.Value = _MaxDate
                    .txtDepthMin.Text = _MinDepth
                    .txtDepthMax.Text = _MaxDepth
                    .txtWidthMin.Text = _MinWidth
                    .txtWidthMax.Text = _MaxWidth
                End With
                Cursor = Cursors.Default
                If frm.ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                    MoreCriteria = New clsMoreCriteria
                    If frm.dtStart.Value <> _MinDate Then MoreCriteria.MinDate = frm.dtStart.Value
                    If frm.dtEnd.Value <> _MaxDate Then MoreCriteria.MaxDate = frm.dtEnd.Value
                    If frm.txtDepthMin.Text <> "" AndAlso Val(frm.txtDepthMin.Text) <> _MinDepth Then MoreCriteria.MinDepth = Val(frm.txtDepthMin.Text)
                    If frm.txtDepthMax.Text <> "" AndAlso Val(frm.txtDepthMax.Text) <> _MaxDepth Then MoreCriteria.MaxDepth = Val(frm.txtDepthMax.Text)
                    If frm.txtWidthMin.Text <> "" AndAlso Val(frm.txtWidthMin.Text) <> _MinWidth Then MoreCriteria.MinWidth = Val(frm.txtWidthMin.Text)
                    If frm.txtWidthMax.Text <> "" AndAlso Val(frm.txtWidthMin.Text) <> _MinWidth Then MoreCriteria.MaxWidth = Val(frm.txtWidthMax.Text)
                    With MoreCriteria
                        For i As Integer = 1 To 4
                            Dim lbx As ListBox = Choose(i, frm.lstCCodes, frm.lstLCodes, frm.lstSCodes, frm.lstQCodes)
                            Dim lst As Generic.List(Of String) = Choose(i, .lstCCodes, .lstLCodes, .lstSCodes, .lstQCodes)
                            lst.Clear()
                            If lbx.SelectedItems.Count > 0 And lbx.SelectedItems.Count < lbx.Items.Count Then
                                For Each s As String In lbx.SelectedItems
                                    lst.Add(IIf(s = "<null>", "", s))
                                Next
                            End If
                        Next
                    End With
                End If
                frm.Dispose()
            End With
            ToolTip1.SetToolTip(btnCriteria, "Additional criteria: " & MoreCriteria.ToString)
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Cursor = Cursors.Default
        End Try
    End Sub

    Private Sub mnuFilter_Click(sender As Object, e As EventArgs) Handles mnuFilter.Click
        If CType(CType(sender, ToolStripMenuItem).GetCurrentParent, ContextMenuStrip).SourceControl Is lstStations Then
            btnStations.PerformClick()
        Else
            btnPCodes.PerformClick()
        End If
    End Sub

    Private Sub mnuSelectAll_Click(sender As Object, e As EventArgs) Handles mnuSelectAll.Click
        If CType(CType(sender, ToolStripMenuItem).GetCurrentParent, ContextMenuStrip).SourceControl Is lstStations Then
            SelectAllStations()
        Else
            SelectAllPCodes()
        End If
    End Sub

    Private Sub btnSegmentsFilter_Click(sender As Object, e As EventArgs) Handles btnStations.Click
        Static FilterString As String = ""
        Dim lst As ListBox = lstStations
        With OpenTables(lstFiles.SelectedIndex)
            Dim dt As DataTable = .GetStationTable(cboBranch.Text)
            FilterList(lst, dt, FilterString)
        End With
    End Sub

    Private Sub btnPCodesFilter_Click(sender As Object, e As EventArgs) Handles btnPCodes.Click
        Static FilterString As String = ""
        Dim lst As ListBox = lstPCodes
        With OpenTables(lstFiles.SelectedIndex)
            Dim lstSta As New Generic.List(Of String)
            For Each s As String In lstStations.SelectedItems
                lstSta.Add(StringSplit(s, " - ")(0))
            Next
            Dim dt As DataTable = .GetPCodeTable(lstSta)
            FilterList(lst, dt, FilterString)
        End With
    End Sub

    Private Sub FilterList(lst As ListBox, dt As DataTable, ByRef FilterString As String)
        With New DataSource.frmFilter(dt)
            .FilterString = FilterString
            If .ShowDialog(Me) = Windows.Forms.DialogResult.OK Then
                FilterString = .FilterString
                lst.BeginUpdate()
                lst.Items.Clear()
                lst.Items.AddRange(.SelectedItems.ToArray)
                lst.EndUpdate()
                If lst.Items.Count > 0 Then lst.SelectedIndex = 0
            End If
            .Dispose()
        End With
    End Sub

    Private Sub btnGIS_Click(sender As Object, e As EventArgs) Handles btnGIS.Click
        'note: this feature has not been fully implemented; decided to control graph from GIS rather then vice versa
        Try
            If GraphForm.GISForm IsNot Nothing Then
                GraphForm.GISForm.Activate()
            Else
                Dim o As System.Runtime.Remoting.ObjectHandle = System.Activator.CreateInstanceFrom("WRDBGISLib.dll", "WRDB.GIS.clsGIS")
                Dim g As WRDB.Interfaces.clsGIS = o.Unwrap
                Dim lst As Generic.List(Of String) = Nothing
                g.ShowGIS(Me, Interfaces.clsGIS.enumMode.SelectPolygons, lst, "")
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub txt_Enter(sender As Object, e As EventArgs) Handles txtStations.Enter, txtPCodes.Enter
        BeginInvoke(New Action(Sub()
                                   CType(sender, TextBox).SelectAll()
                               End Sub))
    End Sub

    Private Sub txtStations_TextChanged(sender As Object, e As EventArgs) Handles txtStations.TextChanged
        FillStationList()
    End Sub

    Private Sub txtPCodes_TextChanged(sender As Object, e As EventArgs) Handles txtPCodes.TextChanged
        FillPCodesList()
    End Sub

End Class